Show a failed-badge behind report links on the artifact index

Each listed report link now carries the failures counter parsed from the
report's Gradle-style index.html (id="failures" info box); reports with
failures get a red "N failed" badge, so a red build reveals which report to
open without clicking through all of them. Pages without such a counter
(Jacoco, profile, documentation) stay unmarked. A full-text FAILED scan was
deliberately not used: even green hsadmin-ng builds contain the word in
embedded test output.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-10 16:44:12 +02:00
co-authored by Claude Fable 5
parent 4ca077da80
commit b351ab33c5
4 changed files with 74 additions and 4 deletions
@@ -209,8 +209,9 @@ class UiController(
/**
* The browsable `index.html` pages under `reports/`, shallowest first; pages nested
* below an already-listed report index are skipped — like the legacy artifact index.
* Each entry carries the report's failures counter for the failed-badge.
*/
private fun reportIndexes(artifactDir: Path): List<String> {
private fun reportIndexes(artifactDir: Path): List<ReportIndexView> {
val reportsDir = artifactDir.resolve("reports")
if (!Files.isDirectory(reportsDir)) {
return emptyList()
@@ -234,6 +235,26 @@ class UiController(
knownDirs += dir
topmost += relativeIndex
}
return topmost
return topmost.map { ReportIndexView(path = it, failures = reportFailures(reportsDir.resolve(it))) }
}
/**
* The failures counter of a Gradle-style HTML test report index
* (`<div class="infoBox" id="failures"><div class="counter">N</div>…`);
* null for report pages without one, e.g. Jacoco or profile reports.
*/
private fun reportFailures(indexFile: Path): Int? =
try {
FAILURES_COUNTER
.find(Files.readString(indexFile))
?.groupValues
?.get(1)
?.toIntOrNull()
} catch (_: Exception) {
null
}
companion object {
private val FAILURES_COUNTER = Regex("""id="failures">\s*<div class="counter">(\d+)""")
}
}
@@ -126,6 +126,16 @@ data class BuildRowView(
}
}
/**
* One report link of the artifact index. [failures] is the failures counter of a
* Gradle-style HTML report index page; null when the page declares none (e.g. Jacoco),
* so only real test reports get a failed-badge.
*/
data class ReportIndexView(
val path: String,
val failures: Int?,
)
/** One card of the current-builds view; the live log is fetched by `gittally.js`. */
data class CurrentBuildView(
val branch: String,
+4 -2
View File
@@ -62,8 +62,10 @@
<h2>Build Artifacts</h2>
<ul th:if="${!#lists.isEmpty(reportIndexes)}">
<li th:each="report : ${reportIndexes}">
<a th:href="${filesBase} + '/reports/' + ${report}" target="_blank"
rel="noopener noreferrer" th:text="'reports/' + ${report}">reports/tests/index.html</a>
<a th:href="${filesBase} + '/reports/' + ${report.path}" target="_blank"
rel="noopener noreferrer" th:text="'reports/' + ${report.path}">reports/tests/index.html</a>
<span th:if="${report.failures != null and report.failures > 0}" class="status status-failed"
th:text="${report.failures} + ' failed'">2 failed</span>
</li>
</ul>
<p th:if="${#lists.isEmpty(reportIndexes)}" class="muted">