Skip to content

Commit

Permalink
Merge pull request #418 from jcrokicki/html-report
Browse files Browse the repository at this point in the history
html report
  • Loading branch information
jcrokicki authored Sep 4, 2020
2 parents db3d3a1 + 0d9e4a2 commit ce34c21
Show file tree
Hide file tree
Showing 5 changed files with 350 additions and 3 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,12 +276,13 @@ The task property `outputFormatter` controls the report output format. The follo
* `"plain"`: format output file as plain text (default)
* `"json"`: format output file as json text
* `"xml"`: format output file as xml text, can be used by other plugins (e.g. sonar)
* `"html"`: format output file as html
* a `Closure`: will be called with the result of the dependency update analysis (see [example below](#custom_report_format))

You can also set multiple output formats using comma as the separator:

```groovy
gradle dependencyUpdates -Drevision=release -DoutputFormatter=json,xml
gradle dependencyUpdates -Drevision=release -DoutputFormatter=json,xml,html
```

The task property `outputDir` controls the output directory for the report file(s). The directory will be created if it does not exist.
Expand Down Expand Up @@ -549,6 +550,13 @@ XML report
</response>
```

HTML report

```
The HTML report provides sections for current, outdated, exceeded and unresolved dependencies.
```


#### <a name="custom_report_format"></a>Custom report format
If you need to create a report in a custom format, you can set the `dependencyUpdates` tasks's `outputFormatter` property to a Closure. The closure will be called with a single argument that is an instance of [com.github.benmanes.gradle.versions.reporter.result.Result](src/main/groovy/com/github/benmanes/gradle/versions/reporter/result/Result.groovy).

Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ apply plugin: 'nexus'
apply plugin: 'codenarc'

group = 'com.github.ben-manes'
version = '0.29.0'
version = '0.30.0'

sourceCompatibility = '1.8'

Expand Down
3 changes: 2 additions & 1 deletion examples/groovy/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ dependencies {
'com.google.code.gson:gson',
'dom4j:dom4j'
upToDate 'backport-util-concurrent:backport-util-concurrent:3.1',
'backport-util-concurrent:backport-util-concurrent-java12:3.1'
'backport-util-concurrent:backport-util-concurrent-java12:3.1',
'org.openjdk.jmh:jmh-parent:1.25.2'
exceedLatest 'com.google.guava:guava:99.0-SNAPSHOT',
'com.google.guava:guava-tests:99.0-SNAPSHOT'
upgradesFound 'com.google.guava:guava:15.0',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
package com.github.benmanes.gradle.versions.reporter

import com.github.benmanes.gradle.versions.reporter.result.DependenciesGroup
import com.github.benmanes.gradle.versions.reporter.result.Dependency
import com.github.benmanes.gradle.versions.reporter.result.DependencyLatest
import com.github.benmanes.gradle.versions.reporter.result.DependencyOutdated
import com.github.benmanes.gradle.versions.reporter.result.DependencyUnresolved
import com.github.benmanes.gradle.versions.reporter.result.Result
import groovy.transform.CompileStatic
import groovy.transform.TupleConstructor

import static com.github.benmanes.gradle.versions.updates.gradle.GradleReleaseChannel.CURRENT
import static com.github.benmanes.gradle.versions.updates.gradle.GradleReleaseChannel.NIGHTLY
import static com.github.benmanes.gradle.versions.updates.gradle.GradleReleaseChannel.RELEASE_CANDIDATE

/**
* An html reporter for the dependency updates results.
*/
@CompileStatic
@TupleConstructor(callSuper = true, includeSuperProperties = true, includeSuperFields = true)
class HtmlReporter extends AbstractReporter {

String header = """
<!DOCTYPE html>
<HEAD><TITLE>Project Dependency Updates Report</TITLE></HEAD>
<style type=\"text/css\">
.body {
font:100% verdana, arial, sans-serif;
background-color:#fff
}
.currentInfo {
border-collapse: collapse;
}
.currentInfo header {
cursor:pointer;
padding: 12px 15px;
}
.currentInfo td {
border: 1px solid black;
padding: 12px 15px;
border-collapse: collapse;
}
.currentInfo tr:nth-child(even) {
background-color: #E4FFB7;
padding: 12px 15px;
border-collapse: collapse;
}
.currentInfo tr:nth-child(odd) {
background-color: #EFFFD2;
padding: 12px 15px;
border-collapse: collapse;
}
.warningInfo {
border-collapse: collapse;
}
.warningInfo header {
cursor:pointer;
padding: 12px 15px;
}
.warningInfo td {
border: 1px solid black;
padding: 12px 15px;
border-collapse: collapse;
}
.warningInfo tr:nth-child(even) {
background-color: #FFFF66;
padding: 12px 15px;
border-collapse: collapse;
}
.warningInfo tr:nth-child(odd) {
background-color: #FFFFCC;
padding: 12px 15px;
border-collapse: collapse;
}
</style>
<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js\"></script>
<script type="text/javascript">
\$(document).ready(function(){
/* set current to collapsed initially */
\$('#currentId').nextUntil('tr.header').slideToggle(100, function(){});
/* click callback to toggle tables */
\$('tr.header').click(function(){
\$(this).find('span').text(function(_, value){return value=='(Click to collapse)'?'(Click to expand)':'(Click to collapse)'});
\$(this).nextUntil('tr.header').slideToggle(100, function(){
});
});
});
</script>
"""

@Override
def write(printStream, Result result) {
writeHeader(printStream)

if (result.count == 0) {
printStream.println '<P>No dependencies found.</P>'
} else {
writeUpToDate(printStream, result)
writeExceedLatestFound(printStream, result)
writeUpgrades(printStream, result)
writeUnresolved(printStream, result)
}

writeGradleUpdates(printStream, result)
}

private def writeHeader(printStream) {
printStream.println header.stripMargin()
}

private def writeUpToDate(printStream, Result result) {
SortedSet<Dependency> versions = result.getCurrent().getDependencies()
if (!versions.isEmpty()) {
printStream.println("<H2>Current dependencies</H2>")
printStream.println("<p>The following dependencies are using the latest ${revision} version:<p>")
printStream.println("<table class=\"currentInfo\">")
getCurrentRows(result).each { printStream.println it }
printStream.println("</table>")
printStream.println("<br>")
}
}

private def getCurrentRows(Result result) {
List<String> rows = new ArrayList<>();
// The following dependencies are using the latest milestone version:
DependenciesGroup<Dependency> list = result.getCurrent();
rows.add("<tr class=\"header\" id = \"currentId\" ><th colspan=\"4\"><b>Current dependencies<span>(Click to expand)</span></b></th></tr>")
rows.add("<tr><td><b>Name</b></td><td><b>Group</b></td><td><b>URL</b></td><td><b>Current Version</b></td></tr>")
for (Dependency item : list.dependencies) {
String rowString;
String rowStringFmt = "<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>"
rowString = String.format(rowStringFmt, item.getName(), item.getGroup(),
getUrlString(item.getProjectUrl()), getVersionString(item.getGroup(), item.getName(), getDisplayableVersion(item)))
rows.add(rowString)
}
return rows
}

private def writeExceedLatestFound(printStream, Result result) {
SortedSet<DependencyLatest> versions = result.getExceeded().getDependencies();
if (!versions.isEmpty()) {
// The following dependencies exceed the version found at the '
// + revision + ' revision level:
printStream.println("<H2>Exceeded dependencies</H2>")
printStream.println("<p>The following dependencies exceed the version found at the ${revision} revision level:<p>")
printStream.println("<table class=\"warningInfo\">")
getExceededRows(result).each { printStream.println it }
printStream.println("</table>")
printStream.println("<br>")
}
}

private def getExceededRows(Result result) {
List<String> rows = new ArrayList<>();
// The following dependencies are using the latest milestone version:
DependenciesGroup<DependencyLatest> list = result.getExceeded()
rows.add("<tr class=\"header\"><th colspan=\"5\"><b>Exceeded dependencies<span>(Click to collapse)</span></b></th></tr>")
rows.add("<tr><td><b>Name</b></td><td><b>Group</b></td><td><b>URL</b></td><td><b>Current Version</b></td><td><b>Latest Version</b></td></tr>")
for (DependencyLatest item : list.dependencies) {
String rowString;
String rowStringFmt = "<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>"
rowString = String.format(rowStringFmt, item.getName(), item.getGroup(),
getUrlString(item.getProjectUrl()), getVersionString(item.getGroup(), item.getName(), item.getVersion()),
getVersionString(item.getGroup(), item.getName(), getDisplayableVersion(item)))
rows.add(rowString)
}
return rows
}

private def writeUpgrades(printStream, Result result) {
SortedSet<DependencyOutdated> versions = result.getOutdated().getDependencies();
if (!versions.isEmpty()) {
printStream.println("<H2>Later dependencies</H2>")
printStream.println("<p>The following dependencies have later ${revision} versions:<p>")
printStream.println("<table class=\"warningInfo\">")
getUpgradesRows(result).each { printStream.println it }
printStream.println("</table>")
printStream.println("<br>")
}
}

private def getUpgradesRows(Result result) {
List<String> rows = new ArrayList<>();
DependenciesGroup<DependencyOutdated> list = result.getOutdated()
rows.add("<tr class=\"header\"><th colspan=\"5\"><b>Later dependencies<span>(Click to collapse)</span></b></th></tr>")
rows.add("<tr><td><b>Name</b></td><td><b>Group</b></td><td><b>URL</b></td><td><b>Current Version</b></td><td><b>Latest Version</b></td></tr>")
for (DependencyOutdated item : list.dependencies) {
String rowString;
String rowStringFmt = "<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>"
rowString = String.format(rowStringFmt, item.getName(), item.getGroup(),
getUrlString(item.getProjectUrl()), getVersionString(item.getGroup(), item.getName(), item.getVersion()),
getVersionString(item.getGroup(), item.getName(), getDisplayableVersion(item)))
rows.add(rowString)
}
return rows
}

private def String getDisplayableVersion(DependencyOutdated item) {
if (getRevision().equalsIgnoreCase("milestone")) {
return item.getAvailable().getMilestone()
} else if (getRevision().equalsIgnoreCase("release")) {
return item.getVersion()
} else if (getRevision().equalsIgnoreCase("integration")) {
return item.getAvailable().getIntegration()
}
return ""
}

private def String getDisplayableVersion(Dependency item) {
return item.getVersion()
}

private def writeUnresolved(printStream, Result result) {
SortedSet<DependencyUnresolved> versions = result.getUnresolved().getDependencies();
if (!versions.isEmpty()) {
printStream.println("<H2>Unresolved dependencies</H2>")
printStream.println("<p>Failed to determine the latest version for the following dependencies:<p>")
printStream.println("<table class=\"warningInfo\">")
getUnresolvedRows(result).each { printStream.println it }
printStream.println("</table>")
printStream.println("<br>")
}
}

private def getUnresolvedRows(Result result) {
List<String> rows = new ArrayList<>();
DependenciesGroup<DependencyUnresolved> list = result.getUnresolved()
rows.add("<tr class=\"header\"><th colspan=\"4\"><b>Unresolved dependencies<span>(Click to collapse)</span></b></th></tr>")
rows.add("<tr><td><b>Name</b></td><td><b>Group</b></td><td><b>URL</b></td><td><b>Current Version</b></td></tr>")
for (DependencyUnresolved item : list.dependencies) {
String rowString;
String rowStringFmt = "<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>"
rowString = String.format(rowStringFmt, item.getName(), item.getGroup(),
getUrlString(item.getProjectUrl()) , getVersionString(item.getGroup(), item.getName(), getDisplayableVersion(item)))
rows.add(rowString)
}
return rows
}

private def writeGradleUpdates(printStream, Result result) {
if (!result.gradle.isEnabled()) {
return
}

printStream.println("<H2>Gradle ${gradleReleaseChannel} updates</H2>")


printStream.println("Gradle ${gradleReleaseChannel} updates:")
result.gradle.with {
// Log Gradle update checking failures.
if (current.isFailure) {
printStream.println("<P>[ERROR] [release channel: ${CURRENT.id}] " + current.reason + "</P>")
}
if ((gradleReleaseChannel == RELEASE_CANDIDATE.id || gradleReleaseChannel == NIGHTLY.id) && releaseCandidate.isFailure) {
printStream.println("<P>[ERROR] [release channel: ${RELEASE_CANDIDATE.id}] " + releaseCandidate.reason + "</P>")
}
if (gradleReleaseChannel == NIGHTLY.id && nightly.isFailure) {
printStream.println("<P>[ERROR] [release channel: ${NIGHTLY.id}] " + nightly.reason + "</P>")
}

// print Gradle updates in breadcrumb format
printStream.print("<P>Gradle: [" + getGradleVersionUrl(running.version))
boolean updatePrinted = false
if (current.isUpdateAvailable && current > running) {
updatePrinted = true
printStream.print(" -> " + getGradleVersionUrl(current.version))
}
if ((gradleReleaseChannel == RELEASE_CANDIDATE.id || gradleReleaseChannel == NIGHTLY.id) && releaseCandidate.isUpdateAvailable && releaseCandidate > current) {
updatePrinted = true
printStream.print(" -> " + getGradleVersionUrl(releaseCandidate.version))
}
if (gradleReleaseChannel == NIGHTLY.id && nightly.isUpdateAvailable && nightly > current) {
updatePrinted = true
printStream.print(" -> " + getGradleVersionUrl(nightly.version))
}
if (!updatePrinted) {
printStream.print(": UP-TO-DATE")
}
printStream.println("]<P>")
printStream.println(getGradleUrl())
}
}

private def getGradleUrl() {
return "<P>For information about Gradle releases click <a target=\"_blank\" href=\"https://gradle.org/releases/\">here</a>.";
}

private def getGradleVersionUrl(String version) {
if (version == null) {
return "https://gradle.org/releases/";
}
return String.format("<a target=\"_blank\" href=\"https://docs.gradle.org/%s/release-notes.html\">%s</a>", version, version)
}

private def getUrlString(String url) {
if (url == null) {
return "";
}
return String.format("<a target=\"_blank\" href=\"%s\">%s</a>", url, url)
}

private def getVersionString(String group, String name, String version) {
String mvn = getMvnVersionString(group, name, version)
String bintray = getBintrayVersionString(group, name, version)
return String.format("%s %s %s", version, mvn, bintray)
}

private def getMvnVersionString(String group, String name, String version) {
// https://search.maven.org/artifact/com.azure/azure-core-http-netty/1.5.4
if (version == null) {
return "";
}
String versionUrl = String.format("https://search.maven.org/artifact/%s/%s/%s/bundle", group, name, version)
return String.format("<a target=\"_blank\" href=\"%s\">%s</a>", versionUrl, "Sonatype")
}

private def getBintrayVersionString(String group, String name, String version) {
// https://bintray.com/bintray/jcenter/com.azure%3Aazure-sdk-template/1.0.3
if (version == null) {
return "";
}
String versionUrl = String.format("https://bintray.com/bintray/jcenter/%s%%3A%s/%s", group, name, version)
return String.format("<a target=\"_blank\" href=\"%s\">%s</a>", versionUrl, "Bintray")
}

@Override
def getFileExtension() {
return 'html'
}
}
Loading

0 comments on commit ce34c21

Please sign in to comment.