Create a report of the global pipeline with after / before results

This create a simple HTML report for each run of the pipeline to
shows which build have degraded with the latest build.

Possible improvement would be to improve the granularity of the report
and the presentation.

Change-Id: Iec1a6fee3c4cb7b80e5d39884681893fe5db6394
diff --git a/jenkins/base/plugins.bzl b/jenkins/base/plugins.bzl
index 263d355..56f5589 100644
--- a/jenkins/base/plugins.bzl
+++ b/jenkins/base/plugins.bzl
@@ -439,4 +439,8 @@
         "0.3.4",
         "b3aa8c11d59a5c1ac007f86a054a259766bc64ee0ad91be0c2dd1981bce3c6f8",
     ],
+    "htmlpublisher": [
+        "1.13",
+        "c5c3d99125110c0d8a63472f6b66b4a2481b78caadbb17632ef86a6f3a19ec4c",
+    ],
 }
diff --git a/jenkins/jobs/global.groovy b/jenkins/jobs/global.groovy
index 5517dd8..2b87682 100644
--- a/jenkins/jobs/global.groovy
+++ b/jenkins/jobs/global.groovy
@@ -117,9 +117,10 @@
   // TODO(dmarting): trigger bazel install everywhere in case of release.
 }
 
-// Then we run all the job in the Global folder but myself
+// Then we run all jobs in the Global folder except the global pipeline job (the current job). 
+report = null
 stage("Test downstream jobs") {
-  runAll(folder: "Global",
+  report = runAll(folder: "Global",
          parameters: [
            [$class: 'TextParameterValue',
             name: 'EXTRA_BAZELRC',
@@ -132,3 +133,7 @@
             value: "${params.REFSPEC}"]
          ])
 }
+
+stage("Publish report") {
+  reportAB report: report, name: "Downstream projects"
+}
diff --git a/jenkins/lib/vars/reportAB.groovy b/jenkins/lib/vars/reportAB.groovy
new file mode 100644
index 0000000..d054a71
--- /dev/null
+++ b/jenkins/lib/vars/reportAB.groovy
@@ -0,0 +1,80 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Step to report failure of a set of jobs and comparing the result with
+// the latest jobs with the same name on another folder
+import build.bazel.ci.JenkinsUtils
+
+@NonCPS
+def formatRun(run) {
+  if (run == null) {
+    return "unkown status"
+  }
+  def resultImg = "${Jenkins.RESOURCE_PATH}/images/16x16/${run.rawBuild.getIconColor()}.png"
+  return "<a href=\"${run.absoluteUrl}console\"><img src=\"${resultImg}\"/></a> <a href=\"${run.absoluteUrl}\">#${run.number}</a>"
+}
+
+@NonCPS
+def collectJobs(report, beforeFolder) {
+  def successes = []
+  def failures = []
+  def alreadyFailing = []
+  for (e in report) {
+    def key = e.key
+    def value = e.value
+    def beforeJob = JenkinsUtils.getLastRun(beforeFolder, key)
+    def element = "${key} ${formatRun(value)} (was ${formatRun(beforeJob)})"
+    if (value.result == "SUCCESS") {
+      successes <<= element
+    } else if (beforeJob == null || beforeJob.rawBuild.result.isWorseOrEqualTo(value.rawBuild.result)) {
+      alreadyFailing <<= element
+    } else {
+      failures <<= element
+    }
+  }
+  return [failures: failures, alreadyFailing: alreadyFailing, successes: successes]
+}
+
+@NonCPS
+def toHTMLList(lst) {
+  def listItems = lst.collect { "<li>${it}</li>" }
+  return "<ul>${listItems.join('\n')}</ul>"
+}
+
+def call(args = [:]) {
+  def name = args.name
+  def jobs = collectJobs(args.report, args.get("beforeFolder", ""))
+  node {
+    writeFile file: (".report/${name}.html"), text: """
+<html>
+<h2>Newly failing jobs</h2>
+<ul>${toHTMLList(jobs.failures)}</ul>
+<h2>Already failing jobs</h2>
+<ul>${toHTMLList(jobs.alreadyFailing)}</ul>
+<h2>Passing jobs</h2>
+<ul>${toHTMLList(jobs.successes)}</ul>
+</html>
+"""
+    publishHTML target: [
+      allowMissing: false,
+      alwaysLinkToLastBuild: false,
+      keepAll: true,
+      reportDir: ".report",
+      reportFiles: "${name}.html",
+      reportName: name
+    ]
+  }
+
+  if (args.get("unstableOnNewFailure", true) && !jobs.failures.empty) {
+    currentBuild.result = "UNSTABLE"
+  }
+}
diff --git a/jenkins/lib/vars/runAll.groovy b/jenkins/lib/vars/runAll.groovy
index 7219cf0..45c38a8 100644
--- a/jenkins/lib/vars/runAll.groovy
+++ b/jenkins/lib/vars/runAll.groovy
@@ -24,6 +24,7 @@
   def statusOnUnstable = params.get("statusOnUnstable", "SUCCESS")
   def jobs = JenkinsUtils.jobs(folder).toArray()
   def toRun = [:]
+  def report = [:]
   for (int k = 0; k < jobs.length; k++) {
     def jobName = jobs[k]
     if (!(jobName in excludes)) {
@@ -35,6 +36,7 @@
           } else if (r.result == "UNSTABLE") {
             currentBuild.result = statusOnUnstable
           }
+          report.put(jobName, r)
         } catch(error) {
           if (catchError) {
             echo "Catched ${error} from upstream job ${jobName}"
@@ -48,4 +50,6 @@
   }
   jobs = null
   parallel(toRun)
+
+  return report
 }