Created
August 1, 2024 10:26
-
-
Save petewilcock/1fef9e373c1dc6d37e7851618f840007 to your computer and use it in GitHub Desktop.
Jenkins Coverage Plugin Report to GitHub Comment
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import groovy.json.JsonSlurper | |
def call() { | |
def coverageUrl = "${env.BUILD_URL}coverage/api/json?pretty=true" | |
echo "Getting ${coverageUrl}" | |
// Fetch the JSON coverage report with authentication | |
def jsonResponse = "" | |
withCredentials([string(credentialsId: 'jenkins-user-credential', variable: 'PASSWORD')]) { | |
def url = new URL(coverageUrl) | |
def connection = (HttpURLConnection) url.openConnection() | |
connection.requestMethod = 'GET' | |
connection.setRequestProperty("Authorization", "Basic " + Base64.getEncoder().encodeToString("jenkins:${PASSWORD}".getBytes())) | |
if (connection.responseCode == 200) { | |
jsonResponse = connection.inputStream.text | |
} else { | |
throw new Exception("Failed to fetch coverage report. HTTP Response Code: ${connection.responseCode}") | |
} | |
connection.disconnect() | |
} | |
// Parse the JSON response | |
def parsedReport = new JsonSlurper().parseText(jsonResponse) | |
// Convert parsed JSON to a markdown report | |
return convertCoverageToMarkdown(parsedReport) | |
} | |
def convertCoverageToMarkdown(parsedReport) { | |
def reportSections = [] | |
def modifiedSectionsEmpty = true | |
// Helper to add section only if it has data | |
def addSectionIfNotEmpty = { title, data -> | |
def section = formatSection(title, data, true) | |
if (section) { | |
reportSections << section | |
modifiedSectionsEmpty = false | |
} | |
} | |
// Check if any 'modified' sections have data | |
addSectionIfNotEmpty("Modified Files Delta", parsedReport.modifiedFilesDelta) | |
addSectionIfNotEmpty("Modified Files Statistics", parsedReport.modifiedFilesStatistics) | |
addSectionIfNotEmpty("Modified Lines Delta", parsedReport.modifiedLinesDelta) | |
addSectionIfNotEmpty("Modified Lines Statistics", parsedReport.modifiedLinesStatistics) | |
// Always add project statistics | |
def projectDelta = formatSection("Project Delta", parsedReport.projectDelta, true) | |
def projectStatistics = formatSection("Project Statistics", parsedReport.projectStatistics, true) | |
if (projectDelta || projectStatistics) { | |
reportSections << (projectDelta ?: '') << (projectStatistics ?: '') | |
} | |
// Add quality gates if present | |
def qualityGates = formatQualityGates(parsedReport.qualityGates) | |
if (qualityGates) { | |
reportSections << qualityGates | |
} | |
// Add the 'no files changed' message if applicable | |
def reportContent = reportSections.join('\n\n').trim() | |
if (modifiedSectionsEmpty) { | |
return "No files in this PR changed the overall Code Coverage level\n\n${reportContent}" | |
} | |
return reportContent | |
} | |
private def formatSection(String title, Map<String, Object> sectionData, boolean applyColorization) { | |
if (!sectionData) { | |
return null | |
} | |
def section = """ | |
### ${title} | |
| Metric | Value | | |
|------------------------------|-----------------:|""" | |
sectionData.each { key, value -> | |
def formattedValue = applyColorization ? formatModifiedValue(value) : value | |
section += "\n| ${getFormattedMetricName(key)} | ${formattedValue} |" | |
} | |
// Ensure that no extra newlines or whitespace are added | |
return section.trim().replaceAll(/(\n)\s{2,}(\|)/, '\n|') // Fix double indentation issues | |
} | |
private def formatModifiedValue(String value) { | |
if (value.startsWith('+')) { | |
return "<span style=\"color:green;\">${value}</span>" | |
} else if (value.startsWith('-')) { | |
return "<span style=\"color:red;\">${value}</span>" | |
} else { | |
return value | |
} | |
} | |
private def getFormattedMetricName(String key) { | |
switch (key) { | |
case "branch": | |
return "Branch Coverage" | |
case "class": | |
return "Class Coverage" | |
case "complexity": | |
return "Complexity" | |
case "complexity-density": | |
return "Complexity Density" | |
case "complexity-maximum": | |
return "Complexity Maximum" | |
case "file": | |
return "File Coverage" | |
case "instruction": | |
return "Instruction Coverage" | |
case "line": | |
return "Line Coverage" | |
case "loc": | |
return "Lines of Code" | |
case "method": | |
return "Method Coverage" | |
case "module": | |
return "Module Coverage" | |
case "package": | |
return "Package Coverage" | |
default: | |
return key.replaceAll(/([a-z])([A-Z])/, '$1 $2').capitalize() | |
} | |
} | |
private def formatQualityGates(Map<String, Object> qualityGates) { | |
if (!qualityGates || !qualityGates.resultItems) { | |
return null | |
} | |
def section = """ | |
### Quality Gates | |
| Quality Gate | Result | Threshold | Value | | |
|-------------------------------------|----------|-----------|---------------:| | |
""" | |
qualityGates.resultItems.each { item -> | |
section += "| ${item.qualityGate} | ${item.result} | ${item.threshold} | ${item.value} |" | |
} | |
// Ensure that no extra newlines or whitespace are added | |
return section.trim().replaceAll(/(\n)\s{2,}(\|)/, '\n|') // Fix double indentation issues | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment