Link index-less report pages from the artifact index (v0.9.8)
Gradle's --profile report is archived but was unreachable: report discovery only looked for index.html, while the profile page carries a timestamped file name. Scan reports/ and its direct sub-directories for HTML pages that no index covers, so a report tree cannot flood the index with inner pages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
632e4396e4
commit
c1869ea427
+1
-1
@@ -11,7 +11,7 @@ plugins {
|
|||||||
group = "de.hoennig"
|
group = "de.hoennig"
|
||||||
// bump at least the patch version for every deployment, so the UI footer
|
// bump at least the patch version for every deployment, so the UI footer
|
||||||
// (BuildProperties) and --version identify what is actually running
|
// (BuildProperties) and --version identify what is actually running
|
||||||
version = "0.9.7"
|
version = "0.9.8"
|
||||||
|
|
||||||
java {
|
java {
|
||||||
toolchain {
|
toolchain {
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ class UiController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The browsable `index.html` pages under `reports/`, shallowest first; pages nested
|
* The browsable report pages under `reports/`, shallowest first; pages nested
|
||||||
* below an already-listed report index are skipped — like the legacy artifact index.
|
* 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.
|
* Each entry carries the report's failures counter for the failed-badge.
|
||||||
*/
|
*/
|
||||||
@@ -248,13 +248,46 @@ class UiController(
|
|||||||
val topmost = mutableListOf<String>()
|
val topmost = mutableListOf<String>()
|
||||||
for (relativeIndex in allIndexes) {
|
for (relativeIndex in allIndexes) {
|
||||||
val dir = relativeIndex.substringBeforeLast('/', "")
|
val dir = relativeIndex.substringBeforeLast('/', "")
|
||||||
if (knownDirs.any { known -> known.isEmpty() || dir == known || dir.startsWith("$known/") }) {
|
if (dir.isCoveredBy(knownDirs)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
knownDirs += dir
|
knownDirs += dir
|
||||||
topmost += relativeIndex
|
topmost += relativeIndex
|
||||||
}
|
}
|
||||||
return topmost.map { ReportIndexView(path = it, failures = reportFailures(reportsDir.resolve(it))) }
|
return (topmost + indexLessReportPages(reportsDir, knownDirs))
|
||||||
|
.map { ReportIndexView(path = it, failures = reportFailures(reportsDir.resolve(it))) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.isCoveredBy(knownDirs: List<String>): Boolean =
|
||||||
|
knownDirs.any { known -> known.isEmpty() || this == known || this.startsWith("$known/") }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report pages of directories without an `index.html`, such as Gradle's `--profile` report
|
||||||
|
* with its timestamped file name. Only `reports/` itself and its direct sub-directories are
|
||||||
|
* scanned, so that a report tree cannot flood the artifact index with its inner pages.
|
||||||
|
*/
|
||||||
|
private fun indexLessReportPages(
|
||||||
|
reportsDir: Path,
|
||||||
|
knownDirs: List<String>,
|
||||||
|
): List<String> {
|
||||||
|
val candidateDirs =
|
||||||
|
buildList {
|
||||||
|
add(reportsDir)
|
||||||
|
Files.list(reportsDir).use { children ->
|
||||||
|
children.asSequence().filter { Files.isDirectory(it) }.forEach { add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return candidateDirs
|
||||||
|
.filterNot { reportsDir.relativize(it).toString().isCoveredBy(knownDirs) }
|
||||||
|
.flatMap { dir ->
|
||||||
|
Files.list(dir).use { pages ->
|
||||||
|
pages
|
||||||
|
.asSequence()
|
||||||
|
.filter { Files.isRegularFile(it) && it.name.endsWith(".html") }
|
||||||
|
.map { reportsDir.relativize(it).toString() }
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
}.sorted()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,6 +7,12 @@
|
|||||||
<div th:replace="~{fragments :: nav(${view})}"></div>
|
<div th:replace="~{fragments :: nav(${view})}"></div>
|
||||||
<div class="panel release-notes">
|
<div class="panel release-notes">
|
||||||
|
|
||||||
|
<h2>v0.9.8 <span class="muted">— 2026-08-10</span></h2>
|
||||||
|
<ul>
|
||||||
|
<li>The artifact index also links report pages of directories without an <code>index.html</code>,
|
||||||
|
such as Gradle's <code>--profile</code> report with its timestamped file name.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<h2>v0.9.7 <span class="muted">— 2026-08-10</span></h2>
|
<h2>v0.9.7 <span class="muted">— 2026-08-10</span></h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><code>init --systemd</code> also installs a nightly Docker cleanup timer
|
<li><code>init --systemd</code> also installs a nightly Docker cleanup timer
|
||||||
|
|||||||
@@ -237,6 +237,29 @@ class UiControllerTest : FunSpec() {
|
|||||||
).andExpect(content().string(not(containsString("reports/tests/test/packages/index.html"))))
|
).andExpect(content().string(not(containsString("reports/tests/test/packages/index.html"))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("artifact index links report pages of directories without an index, but not their inner pages") {
|
||||||
|
val artifactDir = Files.createDirectories(tempDir.resolve("main-abc123-key"))
|
||||||
|
Files.createDirectories(artifactDir.resolve("reports/profile"))
|
||||||
|
Files.writeString(artifactDir.resolve("reports/profile/profile-2026-08-10-18-36-12.html"), "<html></html>")
|
||||||
|
Files.createDirectories(artifactDir.resolve("reports/tests/test"))
|
||||||
|
Files.writeString(artifactDir.resolve("reports/tests/test/index.html"), "<html></html>")
|
||||||
|
Files.createDirectories(artifactDir.resolve("reports/tests/test/classes"))
|
||||||
|
Files.writeString(artifactDir.resolve("reports/tests/test/classes/SomeTest.html"), "<html></html>")
|
||||||
|
every { repository.history() } returns listOf(successResult)
|
||||||
|
every { artifactStore.artifactDir("main-abc123-key") } returns artifactDir
|
||||||
|
|
||||||
|
val page =
|
||||||
|
mockMvc
|
||||||
|
.perform(get("/builds/main-abc123-key"))
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andReturn()
|
||||||
|
.response.contentAsString
|
||||||
|
|
||||||
|
page shouldContain "reports/profile/profile-2026-08-10-18-36-12.html"
|
||||||
|
page shouldContain "reports/tests/test/index.html"
|
||||||
|
page shouldNotContain "SomeTest.html"
|
||||||
|
}
|
||||||
|
|
||||||
test("report links carry a failed-badge from the report's failures counter") {
|
test("report links carry a failed-badge from the report's failures counter") {
|
||||||
val artifactDir = Files.createDirectories(tempDir.resolve("main-abc123-key"))
|
val artifactDir = Files.createDirectories(tempDir.resolve("main-abc123-key"))
|
||||||
Files.createDirectories(artifactDir.resolve("reports/tests/test"))
|
Files.createDirectories(artifactDir.resolve("reports/tests/test"))
|
||||||
|
|||||||
Reference in New Issue
Block a user