Skip to content

Instantly share code, notes, and snippets.

@isurusndr
Forked from x-cray/sonar-report.groovy
Last active April 26, 2021 14:00
Show Gist options
  • Save isurusndr/dd6dc9fd9768f57eb93e6cdad9af7cf3 to your computer and use it in GitHub Desktop.
Save isurusndr/dd6dc9fd9768f57eb93e6cdad9af7cf3 to your computer and use it in GitHub Desktop.
Jenkins email-ext plugin groovy template. Generates daily Sonar violations report grouped by culprits. Updated to work with SonarQube version 5.0+
<!DOCTYPE html>
<head>
<title>Sonar violations report</title>
<style type="text/css">
body
{
margin: 0px;
padding: 15px;
}
body, td, th
{
font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Tahoma, sans-serif;
font-size: 10pt;
}
table
{
border-collapse: collapse;
width: 100%
}
th
{
text-align: left;
}
h1
{
margin-top: 0px;
}
li
{
line-height: 15pt;
}
table.source-incut
{
background-color: #EEE;
border: 1px solid #DDD;
}
table.source-incut td
{
white-space: pre;
padding: 3px;
font-family: "Lucida Console", "Courier New";
font-size: 9pt;
height: 20px;
}
.priority-blocker
{
color: #A22;
}
.priority-critical
{
color: #A42;
}
.priority-major
{
color: #A62;
}
.priority-minor
{
color: #4A2;
}
.priority-info
{
color: #2A2;
}
.line-number
{
color: #777;
width: 20px;
}
.res-name
{
color: #363;
}
.source
{
color: #336;
height: 25px;
}
.error
{
background-color: #FCC;
}
</style>
</head>
<body>
<%
import groovy.json.JsonSlurper
apiSonarRoot = "http://localhost:9000"
webSonarRoot = "http://99x.test.fdvu.net:9000"
def getSonarXml(path) {
format = (path.contains("?") ? "&" : "?") + "format=xml"
sonarUrl = "${apiSonarRoot}${path}${format}"
//println "Reading URL ${sonarUrl} contents"
sonarUrl.toURL().text
}
def getSonarJson(path) {
sonarUrl = "${apiSonarRoot}${path}"
//println "Reading URL ${sonarUrl} contents"
sonarUrl.toURL().text
}
def getParsedResponse(responseXml) {
new XmlSlurper().parseText(responseXml)
}
def getJsonParsedResponse(responseJson) {
new JsonSlurper().parseText(responseJson)
}
def getNewViolatedResources(resKey, qualifier) {
violatedResources = []
resParam = resKey ? "&resource=${resKey}" : ""
allResources = getParsedResponse(getSonarXml("/api/resources?depth=-1&qualifiers=${qualifier}&metrics=new_violations&includetrends=true${resParam}"))
allResources.resource.each { res ->
if (res.msr.var2.toFloat() > 0) {
//println "Adding violated resource ${res.id} [${res.key}] for qualifier ${qualifier}"
violatedResources << res.key.text()
}
}
violatedResources
}
def parseLineMetrics(resources, parser) {
result = []
if (resources?.size() == 1) {
resources?.scm.each { scmLine ->
result << [
line: scmLine[0],
author: scmLine[1],
commitDate: parser(scmLine[2]),
revision: scmLine[3]
]
}
}
result
}
def getDatesByLine(resKey) {
datesResource = getJsonParsedResponse(getSonarJson("/api/sources/scm?commits_by_line=true&key=${resKey}"))
parseLineMetrics(datesResource, { data -> getDateFromString(data) })
}
def getResource(resKey) {
lines = []
contents = getJsonParsedResponse(getSonarJson("/api/sources/show?key=${resKey}"))
if (contents?.sources?.size() > 0) {
contents.sources.each { line ->
lines << line
}
}
lines
}
def getDateFromString(input) {
new Date().parse("yyyy-MM-dd'T'HH:mm:ssZ", input).clearTime()
}
def getResourceViolations(resKey) {
violations = []
today = new Date().clearTime()
contents = getJsonParsedResponse(getSonarJson("/api/issues/search?componentKeys=${resKey}&statuses=OPEN&createdInLast=1w&severities=BLOCKER,CRITICAL,MAJOR,MINOR"))
contents.issues.each { violation ->
createdAt = getDateFromString(violation.creationDate)
//if (createdAt == today) {
datesByLine = getDatesByLine(resKey)
authorsByLine = datesByLine.findAll { entry -> entry.commitDate == today && entry.line == violation.line }.collect { entry -> entry.author }
revisionsByLine = datesByLine.findAll { entry -> entry.commitDate == today && entry.line == violation.line }.collect { entry -> entry.revision }
lineNumText = violation.line
lineNumInt = lineNumText ? lineNumText.toInteger() : -1
if (authorsByLine.size() > 0) {
culprits = authorsByLine.unique()
} else {
culprits = []
if (lineNumInt > -1)
culprits << [violation.author]
}
culpritsText = culprits.size() > 0 ? culprits.join(" or ") : "Someone"
if (revisionsByLine.size() > 0) {
culpritRevisions = revisionsByLine.unique()
} else {
culpritRevisions = []
}
culpritRevisionssText = culpritRevisions.size() > 0 ? culpritRevisions.join(" or ") : "Unknown"
violations << [
key: violation.key,
rule: violation.rule,
message: violation.message,
severity: violation.severity,
line: lineNumInt,
component: violation.component,
author: culpritsText,
revision: culpritRevisionssText
]
//}
}
violations
}
def getUserMappedViolations() {
violatedModules = []
violatedFiles = []
// Get violated projects.
// Step 1. Read violated projects.
projectKeyParam = System.getenv("SONAR_PROJECT_KEY")
violatedProjects = projectKeyParam ? [projectKeyParam] : getNewViolatedResources(null, "TRK")
// Get violated modules.
// Step 2. Read violated modules for projects.
violatedProjects.each { project ->
violatedModules.addAll(getNewViolatedResources(project, "BRC"))
}
// Get violated files.
// Step 3. Read violated files for modules.
violatedModules.each { module ->
violatedFiles.addAll(getNewViolatedResources(module, "FIL"))
}
// Step 4. Group received data per authors.
allViolations = []
violatedFiles.each { file ->
fileViolations = getResourceViolations(file)
fileContents = getResource(file)
fileViolations.each { violation ->
sourceFragment = null
if (violation.line > -1) {
sourceFragment = [:]
for (ln in violation.line - 3..violation.line + 3) {
sourceFragment.put(ln, fileContents[ln - 1])
}
}
allViolations << [violation: violation, resKey: file, source: sourceFragment]
}
}
[allViolations.size(), violatedFiles.size(), allViolations.groupBy { it.violation.author }]
}
%>
<h1>Sonar violations report</h1>
<%
if (build.result == hudson.model.Result.SUCCESS) {
(violationsCount, filesCount, userMappedViolations) = getUserMappedViolations()
if (violationsCount > 0) { %>
<p>Found ${violationsCount} new violations in ${filesCount} files.</p>
<% userMappedViolations.each { author, mappedViolations -> %>
<h2>Violations by ${author}</h2>
<ol>
<% mappedViolations.each { mappedViolation -> %>
<li>
<p>[<b><span class="priority-${mappedViolation.violation.severity?.toLowerCase()}">${mappedViolation.violation.severity}</span></b>] <b>${mappedViolation.violation.rule}</b>. ${mappedViolation.violation.message}<br />
in resource <span class="res-name">${mappedViolation.violation.component}</span> at line ${mappedViolation.violation.line} at commit revision ${mappedViolation.violation.revision}
(<a href="${webSonarRoot}/issues/search#issues=${mappedViolation.violation.key}">view in Sonar</a>):</p>
<% if (mappedViolation.source) { %>
<table class="source-incut" >
<% mappedViolation.source.each { lineNum, line -> %>
<tr<% if (lineNum == mappedViolation.violation.line) { %> class="error"<% } %>>
<td class="line-number" style="height:5">${lineNum}</td><td class="source" style="height:5">${line?.get(1)?.replaceAll('\t', ' ')}</td>
</tr>
<% } %>
</table>
<% } %>
</li>
<% } %>
</ol>
<% } %>
<% } else { %>
<p>No new violations! Woot!!!</p>
<% } %>
<% } else { %>
<p>Build was not successful. Therefore, no report is generated.</p>
<% } %>
</body>
@Supriya111986
Copy link

Hello,
I am using above script but getting below error please look into my issue .It seems in my case http://localhost:9000/api/resources?depth=-1&qualifiers=TRK&metrics=new_violations&includetrends=true&format=xml java.io.FileNotFoundException: http://localhost:9000/api/resources?depth=-1&qualifiers=TRK&metrics=new_violations&includetrends=true&format=xml at not found this api .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment