1
0
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:
yuriy-tereshchuk-sap
2025-07-23 16:45:10 +03:00
committed by GitHub
parent dd0be575c3
commit 6a715b8c16
5 changed files with 229 additions and 24 deletions

View File

@@ -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,

View File

@@ -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.

View File

@@ -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": []
}`

View File

@@ -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)

View File

@@ -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,