You've already forked sap-jenkins-library
mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-09-16 09:26:22 +02:00
feat(sonar): add error entries to sonar report (#5341)
Co-authored-by: Yuriy.Tereshchuk <astro.lutsk.aa@gmail.com>
This commit is contained in:
committed by
GitHub
parent
dd0be575c3
commit
6a715b8c16
@@ -252,23 +252,24 @@ func runSonar(config sonarExecuteScanOptions, client piperhttp.Downloader, runne
|
||||
}
|
||||
// fetch number of issues by severity
|
||||
issueService := SonarUtils.NewIssuesService(serverUrl, config.Token, taskReport.ProjectKey, config.Organization, config.BranchName, config.ChangeID, apiClient)
|
||||
influx.sonarqube_data.fields.blocker_issues, err = issueService.GetNumberOfBlockerIssues()
|
||||
var categories []SonarUtils.Severity
|
||||
influx.sonarqube_data.fields.blocker_issues, err = issueService.GetNumberOfBlockerIssues(&categories)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
influx.sonarqube_data.fields.critical_issues, err = issueService.GetNumberOfCriticalIssues()
|
||||
influx.sonarqube_data.fields.critical_issues, err = issueService.GetNumberOfCriticalIssues(&categories)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
influx.sonarqube_data.fields.major_issues, err = issueService.GetNumberOfMajorIssues()
|
||||
influx.sonarqube_data.fields.major_issues, err = issueService.GetNumberOfMajorIssues(&categories)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
influx.sonarqube_data.fields.minor_issues, err = issueService.GetNumberOfMinorIssues()
|
||||
influx.sonarqube_data.fields.minor_issues, err = issueService.GetNumberOfMinorIssues(&categories)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
influx.sonarqube_data.fields.info_issues, err = issueService.GetNumberOfInfoIssues()
|
||||
influx.sonarqube_data.fields.info_issues, err = issueService.GetNumberOfInfoIssues(&categories)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -280,6 +281,7 @@ func runSonar(config sonarExecuteScanOptions, client piperhttp.Downloader, runne
|
||||
ChangeID: config.ChangeID,
|
||||
BranchName: config.BranchName,
|
||||
Organization: config.Organization,
|
||||
Errors: categories[:],
|
||||
NumberOfIssues: SonarUtils.Issues{
|
||||
Blocker: influx.sonarqube_data.fields.blocker_issues,
|
||||
Critical: influx.sonarqube_data.fields.critical_issues,
|
||||
|
@@ -2,7 +2,9 @@ package sonar
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
sonargo "github.com/magicsong/sonargo/sonar"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -35,6 +37,10 @@ func (service *IssueService) SearchIssues(options *IssuesSearchOption) (*sonargo
|
||||
if err != nil {
|
||||
return nil, response, err
|
||||
}
|
||||
|
||||
// log response
|
||||
log.Entry().Debugf("HTTP Response: %v", func() string { rsp, _ := httputil.DumpResponse(response, true); return string(rsp) }())
|
||||
|
||||
// decode JSON response
|
||||
result := new(sonargo.IssuesSearchObject)
|
||||
err = service.apiClient.decode(response, result)
|
||||
@@ -44,12 +50,11 @@ func (service *IssueService) SearchIssues(options *IssuesSearchOption) (*sonargo
|
||||
return result, response, nil
|
||||
}
|
||||
|
||||
func (service *IssueService) getIssueCount(severity issueSeverity) (int, error) {
|
||||
func (service *IssueService) getIssueCount(severity issueSeverity, categories *[]Severity) (int, error) {
|
||||
options := &IssuesSearchOption{
|
||||
ComponentKeys: service.Project,
|
||||
Severities: severity.ToString(),
|
||||
Resolved: "false",
|
||||
Ps: "1",
|
||||
}
|
||||
if len(service.Organization) > 0 {
|
||||
options.Organization = service.Organization
|
||||
@@ -64,32 +69,49 @@ func (service *IssueService) getIssueCount(severity issueSeverity) (int, error)
|
||||
if err != nil {
|
||||
return -1, errors.Wrapf(err, "failed to fetch the numer of '%s' issues", severity)
|
||||
}
|
||||
|
||||
table := map[string]int{}
|
||||
service.updateIssueTypesTable(result.Issues, table)
|
||||
for issueType, issuesCount := range table {
|
||||
var severityResult Severity
|
||||
severityResult.SeverityType = severity.ToString()
|
||||
severityResult.IssueType = issueType
|
||||
severityResult.IssueCount = issuesCount
|
||||
*categories = append(*categories, severityResult)
|
||||
}
|
||||
return result.Total, nil
|
||||
}
|
||||
|
||||
func (service *IssueService) updateIssueTypesTable(issues []*sonargo.Issue, table map[string]int) {
|
||||
for _, issue := range issues {
|
||||
table[issue.Type]++
|
||||
}
|
||||
delete(table, "") // remove undefined key if any exists in response
|
||||
}
|
||||
|
||||
// GetNumberOfBlockerIssues returns the number of issue with BLOCKER severity.
|
||||
func (service *IssueService) GetNumberOfBlockerIssues() (int, error) {
|
||||
return service.getIssueCount(blocker)
|
||||
func (service *IssueService) GetNumberOfBlockerIssues(categories *[]Severity) (int, error) {
|
||||
return service.getIssueCount(blocker, categories)
|
||||
}
|
||||
|
||||
// GetNumberOfCriticalIssues returns the number of issue with CRITICAL severity.
|
||||
func (service *IssueService) GetNumberOfCriticalIssues() (int, error) {
|
||||
return service.getIssueCount(critical)
|
||||
func (service *IssueService) GetNumberOfCriticalIssues(categories *[]Severity) (int, error) {
|
||||
return service.getIssueCount(critical, categories)
|
||||
}
|
||||
|
||||
// GetNumberOfMajorIssues returns the number of issue with MAJOR severity.
|
||||
func (service *IssueService) GetNumberOfMajorIssues() (int, error) {
|
||||
return service.getIssueCount(major)
|
||||
func (service *IssueService) GetNumberOfMajorIssues(categories *[]Severity) (int, error) {
|
||||
return service.getIssueCount(major, categories)
|
||||
}
|
||||
|
||||
// GetNumberOfMinorIssues returns the number of issue with MINOR severity.
|
||||
func (service *IssueService) GetNumberOfMinorIssues() (int, error) {
|
||||
return service.getIssueCount(minor)
|
||||
func (service *IssueService) GetNumberOfMinorIssues(categories *[]Severity) (int, error) {
|
||||
return service.getIssueCount(minor, categories)
|
||||
}
|
||||
|
||||
// GetNumberOfInfoIssues returns the number of issue with INFO severity.
|
||||
func (service *IssueService) GetNumberOfInfoIssues() (int, error) {
|
||||
return service.getIssueCount(info)
|
||||
func (service *IssueService) GetNumberOfInfoIssues(categories *[]Severity) (int, error) {
|
||||
return service.getIssueCount(info, categories)
|
||||
}
|
||||
|
||||
// NewIssuesService returns a new instance of a service for the issues API endpoint.
|
||||
|
@@ -26,9 +26,12 @@ func TestIssueService(t *testing.T) {
|
||||
httpmock.RegisterResponder(http.MethodGet, testURL+"/api/"+EndpointIssuesSearch+"", httpmock.NewStringResponder(http.StatusOK, responseIssueSearchCritical))
|
||||
// create service instance
|
||||
serviceUnderTest := NewIssuesService(testURL, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, sender)
|
||||
// Severities
|
||||
var severities []Severity
|
||||
// test
|
||||
count, err := serviceUnderTest.GetNumberOfBlockerIssues()
|
||||
count, err := serviceUnderTest.GetNumberOfBlockerIssues(&severities)
|
||||
// assert
|
||||
assert.Equal(t, []Severity{{SeverityType: "BLOCKER", IssueType: "CODE_SMELL", IssueCount: 1}}, severities)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 111, count)
|
||||
assert.Equal(t, 1, httpmock.GetTotalCallCount(), "unexpected number of requests")
|
||||
@@ -43,14 +46,16 @@ func TestIssueService(t *testing.T) {
|
||||
httpmock.RegisterResponder(http.MethodGet, testURL+"/api/"+EndpointIssuesSearch+"", httpmock.NewStringResponder(http.StatusNotFound, responseIssueSearchError))
|
||||
// create service instance
|
||||
serviceUnderTest := NewIssuesService(testURL, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, sender)
|
||||
// Severities
|
||||
var severities []Severity
|
||||
// test
|
||||
count, err := serviceUnderTest.GetNumberOfCriticalIssues()
|
||||
count, err := serviceUnderTest.GetNumberOfCriticalIssues(&severities)
|
||||
// assert
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, -1, count)
|
||||
assert.Equal(t, 1, httpmock.GetTotalCallCount(), "unexpected number of requests")
|
||||
})
|
||||
t.Run("", func(t *testing.T) {
|
||||
t.Run("multiple severities", func(t *testing.T) {
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
@@ -60,17 +65,51 @@ func TestIssueService(t *testing.T) {
|
||||
httpmock.RegisterResponder(http.MethodGet, testURL+"/api/"+EndpointIssuesSearch+"", httpmock.NewStringResponder(http.StatusOK, responseIssueSearchCritical))
|
||||
// create service instance
|
||||
serviceUnderTest := NewIssuesService(testURL, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, sender)
|
||||
// Severities
|
||||
var severities []Severity
|
||||
// test
|
||||
countMajor, err := serviceUnderTest.GetNumberOfMajorIssues()
|
||||
countMinor, err := serviceUnderTest.GetNumberOfMinorIssues()
|
||||
countInfo, err := serviceUnderTest.GetNumberOfInfoIssues()
|
||||
countMajor, err := serviceUnderTest.GetNumberOfMajorIssues(&severities)
|
||||
countMinor, err := serviceUnderTest.GetNumberOfMinorIssues(&severities)
|
||||
countInfo, err := serviceUnderTest.GetNumberOfInfoIssues(&severities)
|
||||
// assert
|
||||
assert.Equal(t, []Severity{
|
||||
{SeverityType: "MAJOR", IssueType: "CODE_SMELL", IssueCount: 1},
|
||||
{SeverityType: "MINOR", IssueType: "CODE_SMELL", IssueCount: 1},
|
||||
{SeverityType: "INFO", IssueType: "CODE_SMELL", IssueCount: 1},
|
||||
}, severities)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 111, countMajor)
|
||||
assert.Equal(t, 111, countMinor)
|
||||
assert.Equal(t, 111, countInfo)
|
||||
assert.Equal(t, 3, httpmock.GetTotalCallCount(), "unexpected number of requests")
|
||||
})
|
||||
t.Run("multiple issues", func(t *testing.T) {
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
sender := &piperhttp.Client{}
|
||||
sender.SetOptions(piperhttp.ClientOptions{MaxRetries: -1, UseDefaultTransport: true})
|
||||
// add response handler
|
||||
httpmock.RegisterResponder(http.MethodGet, testURL+"/api/"+EndpointIssuesSearch+"", httpmock.NewStringResponder(http.StatusOK, responseIssueSearchBug))
|
||||
// create service instance
|
||||
serviceUnderTest := NewIssuesService(testURL, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, sender)
|
||||
// Severities
|
||||
var severities []Severity
|
||||
// test
|
||||
countMajor, err := serviceUnderTest.GetNumberOfMajorIssues(&severities)
|
||||
countMinor, err := serviceUnderTest.GetNumberOfMinorIssues(&severities)
|
||||
// assert
|
||||
assert.Equal(t, []Severity{
|
||||
{SeverityType: "MAJOR", IssueType: "CODE_SMELL", IssueCount: 1},
|
||||
{SeverityType: "MAJOR", IssueType: "BUG", IssueCount: 1},
|
||||
{SeverityType: "MINOR", IssueType: "CODE_SMELL", IssueCount: 1},
|
||||
{SeverityType: "MINOR", IssueType: "BUG", IssueCount: 1},
|
||||
}, severities)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 111, countMajor)
|
||||
assert.Equal(t, 111, countMinor)
|
||||
assert.Equal(t, 2, httpmock.GetTotalCallCount(), "unexpected number of requests")
|
||||
})
|
||||
}
|
||||
|
||||
const responseIssueSearchError = `{
|
||||
@@ -165,3 +204,131 @@ const responseIssueSearchCritical = `{
|
||||
],
|
||||
"facets": []
|
||||
}`
|
||||
|
||||
const responseIssueSearchBug = `{
|
||||
"total": 111,
|
||||
"p": 1,
|
||||
"ps": 1,
|
||||
"paging": {
|
||||
"pageIndex": 1,
|
||||
"pageSize": 1,
|
||||
"total": 111
|
||||
},
|
||||
"effortTotal": 1176,
|
||||
"debtTotal": 1176,
|
||||
"issues": [
|
||||
{
|
||||
"key": "AXW3MmCVOYWf3_DBLGvL",
|
||||
"rule": "go:S3776",
|
||||
"severity": "CRITICAL",
|
||||
"component": "SAP_jenkins-library:cmd/fortifyExecuteScan.go",
|
||||
"project": "SAP_jenkins-library",
|
||||
"line": 647,
|
||||
"hash": "a154a51bdb1502a2ac057a348d08e7f6",
|
||||
"textRange": {
|
||||
"startLine": 647,
|
||||
"endLine": 647,
|
||||
"startOffset": 5,
|
||||
"endOffset": 23
|
||||
},
|
||||
"flows": [
|
||||
{
|
||||
"locations": [
|
||||
{
|
||||
"component": "SAP_jenkins-library:cmd/fortifyExecuteScan.go",
|
||||
"textRange": {
|
||||
"startLine": 651,
|
||||
"endLine": 651,
|
||||
"startOffset": 1,
|
||||
"endOffset": 3
|
||||
},
|
||||
"msg": "+1"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"status": "OPEN",
|
||||
"message": "Refactor this method to reduce its Cognitive Complexity from 16 to the 15 allowed.",
|
||||
"effort": "6min",
|
||||
"debt": "6min",
|
||||
"assignee": "CCFenner@github",
|
||||
"author": "33484802+olivernocon@users.noreply.github.com",
|
||||
"tags": [],
|
||||
"creationDate": "2020-11-11T11:06:04+0100",
|
||||
"updateDate": "2020-11-11T11:06:04+0100",
|
||||
"type": "CODE_SMELL",
|
||||
"organization": "sap-1"
|
||||
},
|
||||
|
||||
{
|
||||
"key": "AXW3MmCVOYWf3_DBLGvL",
|
||||
"rule": "go:S3776",
|
||||
"severity": "CRITICAL",
|
||||
"component": "SAP_jenkins-library:cmd/fortifyExecuteScan.go",
|
||||
"project": "SAP_jenkins-library",
|
||||
"line": 647,
|
||||
"hash": "a154a51bdb1502a2ac057a348d08e7f6",
|
||||
"textRange": {
|
||||
"startLine": 647,
|
||||
"endLine": 647,
|
||||
"startOffset": 5,
|
||||
"endOffset": 23
|
||||
},
|
||||
"flows": [
|
||||
{
|
||||
"locations": [
|
||||
{
|
||||
"component": "SAP_jenkins-library:cmd/fortifyExecuteScan.go",
|
||||
"textRange": {
|
||||
"startLine": 651,
|
||||
"endLine": 651,
|
||||
"startOffset": 1,
|
||||
"endOffset": 3
|
||||
},
|
||||
"msg": "+1"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"status": "OPEN",
|
||||
"message": "Refactor this method to reduce its Cognitive Complexity from 16 to the 15 allowed.",
|
||||
"effort": "6min",
|
||||
"debt": "6min",
|
||||
"assignee": "CCFenner@github",
|
||||
"author": "33484802+olivernocon@users.noreply.github.com",
|
||||
"tags": [],
|
||||
"creationDate": "2020-11-11T11:06:04+0100",
|
||||
"updateDate": "2020-11-11T11:06:04+0100",
|
||||
"type": "BUG",
|
||||
"organization": "sap-1"
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"organization": "sap-1",
|
||||
"key": "SAP_jenkins-library:cmd/fortifyExecuteScan.go",
|
||||
"uuid": "AXVKXJIlrkwsFznOfAie",
|
||||
"enabled": true,
|
||||
"qualifier": "FIL",
|
||||
"name": "fortifyExecuteScan.go",
|
||||
"longName": "cmd/fortifyExecuteScan.go",
|
||||
"path": "cmd/fortifyExecuteScan.go"
|
||||
},
|
||||
{
|
||||
"organization": "sap-1",
|
||||
"key": "SAP_jenkins-library",
|
||||
"uuid": "AXVFg_8dh6o1O3pu_MCx",
|
||||
"enabled": true,
|
||||
"qualifier": "TRK",
|
||||
"name": "jenkins-library",
|
||||
"longName": "jenkins-library"
|
||||
}
|
||||
],
|
||||
"organizations": [
|
||||
{
|
||||
"key": "sap-1",
|
||||
"name": "SAP"
|
||||
}
|
||||
],
|
||||
"facets": []
|
||||
}`
|
||||
|
@@ -17,6 +17,7 @@ type ReportData struct {
|
||||
BranchName string `json:"branchName,omitempty"`
|
||||
Organization string `json:"organization,omitempty"`
|
||||
NumberOfIssues Issues `json:"numberOfIssues"`
|
||||
Errors []Severity `json:"errors"`
|
||||
Coverage *SonarCoverage `json:"coverage,omitempty"`
|
||||
LinesOfCode *SonarLinesOfCode `json:"linesOfCode,omitempty"`
|
||||
}
|
||||
@@ -30,6 +31,12 @@ type Issues struct {
|
||||
Info int `json:"info"`
|
||||
}
|
||||
|
||||
type Severity struct {
|
||||
SeverityType string `json:"severity"`
|
||||
IssueType string `json:"error_type,omitempty"`
|
||||
IssueCount int `json:"issues,omitempty"`
|
||||
}
|
||||
|
||||
// WriteReport ...
|
||||
func WriteReport(data ReportData, reportPath string, writeToFile func(f string, d []byte, p os.FileMode) error) error {
|
||||
jsonData, err := json.Marshal(data)
|
||||
|
@@ -22,11 +22,18 @@ func writeToFileMock(f string, d []byte, p os.FileMode) error {
|
||||
|
||||
func TestWriteReport(t *testing.T) {
|
||||
// init
|
||||
const expected = `{"serverUrl":"https://sonarcloud.io","projectKey":"Piper-Validation/Golang","taskId":"mock.Anything","numberOfIssues":{"blocker":0,"critical":1,"major":2,"minor":3,"info":4},"coverage":{"coverage":13.7,"lineCoverage":37.1,"linesToCover":123,"uncoveredLines":23,"branchCoverage":42,"branchesToCover":30,"uncoveredBranches":3},"linesOfCode":{"total":327,"languageDistribution":[{"languageKey":"java","linesOfCode":327}]}}`
|
||||
const expected = `{"serverUrl":"https://sonarcloud.io","projectKey":"Piper-Validation/Golang","taskId":"mock.Anything","numberOfIssues":{"blocker":0,"critical":1,"major":2,"minor":3,"info":4},"errors":[{"severity":"CRITICAL","error_type":"CODE_SMELL","issues":10}],"coverage":{"coverage":13.7,"lineCoverage":37.1,"linesToCover":123,"uncoveredLines":23,"branchCoverage":42,"branchesToCover":30,"uncoveredBranches":3},"linesOfCode":{"total":327,"languageDistribution":[{"languageKey":"java","linesOfCode":327}]}}`
|
||||
testData := ReportData{
|
||||
ServerURL: "https://sonarcloud.io",
|
||||
ProjectKey: "Piper-Validation/Golang",
|
||||
TaskID: mock.Anything,
|
||||
Errors: []Severity{
|
||||
{
|
||||
SeverityType: "CRITICAL",
|
||||
IssueType: "CODE_SMELL",
|
||||
IssueCount: 10,
|
||||
},
|
||||
},
|
||||
NumberOfIssues: Issues{
|
||||
Critical: 1,
|
||||
Major: 2,
|
||||
|
Reference in New Issue
Block a user