Skip to content

Instantly share code, notes, and snippets.

@x-cray
Last active July 3, 2019 09:23
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save x-cray/2828324 to your computer and use it in GitHub Desktop.
Save x-cray/2828324 to your computer and use it in GitHub Desktop.
Jenkins email-ext plugin groovy template. Generates daily Sonar violations report grouped by culprits.
<!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;
}
.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;
}
.error
{
background-color: #FCC;
}
</style>
</head>
<body>
<%
apiSonarRoot = "http://localhost:9000"
webSonarRoot = "http://gateway:9000"
def getSonarXml(path) {
format = (path.contains("?") ? "&" : "?") + "format=xml"
sonarUrl = "${apiSonarRoot}${path}${format}"
//println "Reading URL ${sonarUrl} contents"
sonarUrl.toURL().text
}
def getParsedResponse(responseXml) {
new XmlSlurper().parseText(responseXml)
}
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?.resource?.size() == 1) {
resources.resource[0].msr.data.text().split(";").each { line ->
(lineNum, data) = line.split("=")
result.put(lineNum.toInteger(), parser(data))
}
}
result
}
def getAuthorsByLine(resKey) {
authorsResource = getParsedResponse(getSonarXml("/api/resources?resource=${resKey}&metrics=authors_by_line"))
parseLineMetrics(authorsResource, { data -> data })
}
def getDatesByLine(resKey) {
datesResource = getParsedResponse(getSonarXml("/api/resources?resource=${resKey}&metrics=last_commit_datetimes_by_line"))
parseLineMetrics(datesResource, { data -> getDateFromString(data) })
}
def getResource(resKey) {
lines = []
contents = getParsedResponse(getSonarXml("/api/sources?resource=${resKey}"))
if (contents?.line?.size() > 0) {
contents.line.each { line ->
lines << line.val.text()
}
}
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 = getParsedResponse(getSonarXml("/api/violations?resource=${resKey}"))
contents.violation.each { violation ->
createdAt = getDateFromString(violation.createdAt.text())
if (createdAt == today) {
authorsByLine = getAuthorsByLine(resKey)
datesByLine = getDatesByLine(resKey)
linesModifiedToday = datesByLine.findAll { entry -> entry.value == today }.collect { entry -> entry.key }
lineNumText = violation.line.text()
lineNumInt = lineNumText ? lineNumText.toInteger() : -1
if (linesModifiedToday.size() > 0) {
culprits = linesModifiedToday.collect { line -> authorsByLine[line] }.unique()
} else {
culprits = []
if (lineNumInt > -1)
culprits << authorsByLine[lineNumInt]
}
culpritsText = culprits.size() > 0 ? culprits.join(" or ") : "Someone"
violations << [
rule: violation.rule.name.text(),
message: violation.message.text(),
priority: violation.priority.text(),
lineNum: lineNumInt,
authors: culpritsText
]
}
}
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.lineNum > -1) {
sourceFragment = [:]
for (ln in violation.lineNum - 3..violation.lineNum + 3) {
sourceFragment.put(ln, fileContents[ln - 1])
}
}
allViolations << [violation: violation, resKey: file, source: sourceFragment]
}
}
[allViolations.size(), violatedFiles.size(), allViolations.groupBy { it.violation.authors }]
}
%>
<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 { authors, mappedViolations -> %>
<h2>Violations by ${authors}</h2>
<ol>
<% mappedViolations.each { mappedViolation -> %>
<li>
<p>[<b><span class="priority-${mappedViolation.violation.priority.toLowerCase()}">${mappedViolation.violation.priority}</span></b>] <b>${mappedViolation.violation.rule}</b>. ${mappedViolation.violation.message}<br />
in resource <span class="res-name">${mappedViolation.resKey}</span> at line ${mappedViolation.violation.lineNum}
(<a href="${webSonarRoot}/drilldown/measures/${mappedViolation.resKey}?metric=new_violations#L${mappedViolation.violation.lineNum}">view in Sonar</a>):</p>
<% if (mappedViolation.source) { %>
<table class="source-incut">
<% mappedViolation.source.each { lineNum, line -> %>
<tr<% if (lineNum == mappedViolation.violation.lineNum) { %> class="error"<% } %>>
<td class="line-number">${lineNum}</td><td class="source">${line.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>
Copy link

ghost commented May 1, 2018

Hi :) i seem to be getting an exception error, is there any way around it , for the template to work? ive attached the error log. i checked the root and set the localhost to my local sonar server, i must be missing something.
error capture

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