From ca0a010f7b0af05aae27c24a025bf21ccddd2439 Mon Sep 17 00:00:00 2001
From: Frank Viernau
Date: Fri, 10 May 2024 16:57:12 +0200
Subject: [PATCH] feat(static-html): Re-design the project table
Previously, the project table view had the following aspects which
are changed in this commit:
1. One dedicated column for each, analyzer and scanner issues.
2. The color and background color of each row is derived from the
worst severity issue contained in that row. For example,
if a row contains a hint and an error, the entire row shows in red,
which is the color associated with an error.
3. If the entire project is excluded, the view looks the same, except
for the opacity which is reduced a bit.
This is changed as follows:
1. One dedicated column for each open, resolved, and excluded issues.
2. The issue view is limited to just the issue columns, whereas each
issue is shown in a color indicating its severity or state. The
colors are gray (excluded), green (resolved), red (error), yellow
(warning) and blue (hint). So, the state of an issue no more affects
the color of the entire row.
3. If the entire project is excluded, the opacity is still used to
indicate that. However, the opacity of the issue columns is not
affected anymore, because this looks nicer and the issues now
properly indicate their exclusion anyway.
4. In order to visually establish a border between the issues, the view
of the issue is changed to a card-view.
Note: It is now possile to quickly grasp the open issues for each
project in order of the severity, due to the use of the dedicated
column for open issues. Furthermore, it is easy to add the ability
of showing also the advisor issues (in an upcoming change) without
the need to add another (advisor issues) column.
Signed-off-by: Frank Viernau
---
...ic-html-reporter-test-expected-output.html | 869 +++++++++---------
.../src/main/kotlin/StaticHtmlReporter.kt | 152 +--
.../src/main/kotlin/TablesReport.kt | 7 +-
.../main/kotlin/TablesReportModelMapper.kt | 34 +-
.../main/resources/static-html-reporter.css | 73 +-
5 files changed, 588 insertions(+), 547 deletions(-)
diff --git a/plugins/reporters/static-html/src/funTest/assets/static-html-reporter-test-expected-output.html b/plugins/reporters/static-html/src/funTest/assets/static-html-reporter-test-expected-output.html
index 8de1dad1f4c4c..c294869c8a9bb 100644
--- a/plugins/reporters/static-html/src/funTest/assets/static-html-reporter-test-expected-output.html
+++ b/plugins/reporters/static-html/src/funTest/assets/static-html-reporter-test-expected-output.html
@@ -152,57 +152,70 @@
border-right: 1px solid rgba(34, 36, 38, .15);
}
-.report-table li.resolved {
- color: #2c662d;
-}
-
.report-table details {
overflow: scroll;
}
-.report-table tr.error {
+.report-table tr.error td {
background: #fff6f6;
color: #9f3a38;
}
-.report-table tr.warning {
+.report-table tr.warning td {
background: #fffaf3;
color: #573a08;
}
-.report-table tr.hint {
+.report-table tr.hint td {
background: #f7f5ff;
color: #1c0859;
}
/**
- * Rule violation table, which specializes .report-table.
+ * Project section.
*/
-.report-rule-violation-table tr.resolved {
- background: #fcfff5;
- color: #2c662d;
+.project.excluded {
+ h3, .report-key-value-table, .report-project-table > thead th {
+ filter: opacity(50%);
+ }
}
-/**
- * Project table, which specializes .report-table.
- */
+.report-project-table .pkg.excluded {
+ > td:nth-child(1), td:nth-child(2), td:nth-child(3), td:nth-child(4) {
+ filter: opacity(50%);
+ }
+}
-.report-project-table tr.error {
- color: black;
+.report-project-table .pkg:not(.excluded) {
+ .scope.excluded, .detected-license.excluded {
+ filter: opacity(50%);
+ }
}
-.report-project-table tr.error td:nth-child(5),
-.report-project-table tr.error td:nth-child(6) {
- color: #9f3a38;
+.report-project-table td:has(.package-issue-table) {
+ padding: 0px;
}
-/*
- * Excluded state.
- */
+.package-issue-table {
+ border-spacing: 10px;
+ padding: 0px;
+ margin: 0px;
+}
-.excluded {
- filter: opacity(50%);
+.package-issue-table td {
+ border-radius: .60rem !important;
+ border-bottom: 1px solid rgba(34, 36, 38, .15);
+}
+
+.package-issue-table tr.resolved td {
+ background: #fcfff5;
+ color: #2c662d;
+}
+
+.package-issue-table tr.excluded td {
+ background: #f9fafb;
+ color: rgba(34, 36, 38, .7);
}
.reason {
@@ -213,18 +226,6 @@
display: inline;
}
-table.excluded tr.excluded {
- filter: opacity(100%);
-}
-
-table tr.excluded td li.excluded {
- filter: opacity(100%);
-}
-
-table.excluded tr.excluded td li.excluded {
- filter: opacity(100%);
-}
-
/**
* Report table media specific styling.
*/
@@ -832,398 +833,410 @@ Advisor Issue Summary (1 errors, 1 warnings, 1 hi
-
- Project is Excluded
-
The project is excluded for the following reason(s):
-
-
EXAMPLE_OF - The project is an example.
-
- VCS Information
-
-
-
- Type |
- Git |
-
-
- URL |
- https://example.com/git |
-
-
- Path |
- project |
-
-
- Revision |
- master |
-
-
-
- Packages
-
-
-
- # |
- Package |
- Scopes |
- Licenses |
- Analyzer Issues |
- Scanner Issues |
-
-
-
-
- 1 |
- Gradle:org.ossreviewtoolkit:nested-fake-project:1.0.0 |
- |
- Declared Licenses:
- -
-
-
-
- Detected Licenses (from VCS):
- -
-
-
MIT (Excluded: EXAMPLE_OF - These are example files.)
-
- Effective License:
- -
-
-
-
- |
-
-
- |
-
-
- -
-
2024-04-22T10:36:10.661544294Z [ERROR]: FakeScanner - ERROR: Timeout after 300 seconds
- while scanning file 'project/file-within-excluded-project.dat'.
-
- -
-
2024-04-25T07:44:20.725613974Z [HINT]: FakeScanner - Example hint.
-
- -
-
2024-04-25T07:44:20.725613974Z [WARNING]: FakeScanner - Example warning.
-
- -
-
2024-04-25T07:44:20.725613974Z [ERROR]: FakeScanner - Example error.
-
- -
-
2024-04-25T07:44:20.725613974Z [ERROR]: FakeScanner - Example error, resolved.
-
- Resolved by: CANT_FIX_ISSUE - Resolved for illustration.
-
-
- |
-
-
-
-
- VCS Information
-
-
-
- Type |
- Git |
-
-
- URL |
- https://github.com/oss-review-toolkit/ort.git |
-
-
- Path |
- analyzer/src/funTest/assets/projects/synthetic/gradle/lib |
-
-
- Revision |
- master |
-
-
-
- Packages
-
-
-
- # |
- Package |
- Scopes |
- Licenses |
- Analyzer Issues |
- Scanner Issues |
-
-
-
-
- 1 |
- Gradle:org.ossreviewtoolkit.gradle.example:lib:1.0.0 |
- |
- Detected Licenses (from VCS):
- -
-
-
-
LicenseRef-test-Apache-2.0-multi-line ( link to the location)
- LicenseRef-test-Apache-2.0-single-line (exemplary link to the first of 2 locations)
-
-
- Effective License:
- -
-
-
-
- |
-
-
- -
-
2024-04-25T07:44:20.725613974Z [HINT]: Gradle - Example hint.
-
- -
-
2024-04-25T07:44:20.725613974Z [WARNING]: Gradle - Example warning.
-
- -
-
2024-04-25T07:44:20.725613974Z [ERROR]: Gradle - Example error.
-
- -
-
2024-04-25T07:44:20.725613974Z [ERROR]: Gradle - Example error, resolved.
-
- Resolved by: CANT_FIX_ISSUE - Resolved for illustration.
-
-
- |
-
-
- -
-
Unknown time [ERROR]: Dummy - DownloadException: No source artifact URL provided for
- 'Gradle:org.ossreviewtoolkit.gradle.example:lib:1.0.0'. Caused by: DownloadException: No VCS URL provided for 'Gradle:org.ossreviewtoolkit.gradle.example:lib:1.0.0'.
- Please make sure the published POM file includes the SCM connection, see: https://docs.gradle.org/current/userguide/publishing_maven.html#sec:modifying_the_generated_pom
-
- -
-
2024-04-22T10:36:10.661544294Z [ERROR]: FakeScanner - ERROR: Timeout after 300 seconds
- while scanning file 'analyzer/src/funTest/assets/projects/synthetic/gradle/lib/included-file.dat'.
-
- -
-
2024-04-25T07:44:20.725613974Z [HINT]: FakeScanner - Example hint.
-
- -
-
2024-04-25T07:44:20.725613974Z [WARNING]: FakeScanner - Example warning.
-
- -
-
2024-04-25T07:44:20.725613974Z [ERROR]: FakeScanner - Example error.
-
- -
-
2024-04-25T07:44:20.725613974Z [ERROR]: FakeScanner - Example error, resolved.
-
- Resolved by: CANT_FIX_ISSUE - Resolved for illustration.
-
-
- |
-
-
- 2 |
- Ant:junit:junit:4.12 |
-
-
- |
- Declared Licenses:
- -
-
-
- Effective License:
- -
-
-
-
- |
-
-
- |
-
-
- |
-
-
- 3 |
- Maven:com.foobar:foobar:1.0 |
-
-
- |
- Concluded License:
- -
-
-
- Declared Licenses:
- -
-
-
-
- Effective License:
- -
-
-
-
- |
-
-
- |
-
-
- |
-
-
- 4 |
- Maven:com.h2database:h2:1.4.200 |
-
-
- |
- Concluded License:
- -
-
-
- Declared Licenses:
- -
-
-
-
- Effective License:
- -
-
-
-
- |
-
-
- |
-
-
- |
-
-
- 5 |
- Maven:org.apache.commons:commons-lang3:3.5 |
-
-
- |
- Declared Licenses:
- -
-
-
- Effective License:
- -
-
-
-
- |
-
-
- |
-
-
- |
-
-
- 6 |
- Maven:org.apache.commons:commons-text:1.1 |
-
-
- |
- Declared Licenses:
- -
-
-
- Effective License:
- -
-
-
-
- |
-
-
- |
-
-
- |
-
-
- 7 |
- Maven:org.example.test:component:1.11 |
-
-
- |
- |
-
-
- |
-
-
- |
-
-
- 8 |
- Maven:org.hamcrest:hamcrest-core:1.3 |
-
-
- |
- Declared Licenses:
- -
-
-
- Effective License:
- -
-
-
-
- |
-
-
- |
-
-
- |
-
-
-
+
+
Repository Configuration
---
diff --git a/plugins/reporters/static-html/src/main/kotlin/StaticHtmlReporter.kt b/plugins/reporters/static-html/src/main/kotlin/StaticHtmlReporter.kt
index 53351541dee16..5cc112b45a88d 100644
--- a/plugins/reporters/static-html/src/main/kotlin/StaticHtmlReporter.kt
+++ b/plugins/reporters/static-html/src/main/kotlin/StaticHtmlReporter.kt
@@ -389,83 +389,79 @@ class StaticHtmlReporter : Reporter {
private fun DIV.projectTable(table: ProjectTable) {
val excludedClass = "excluded".takeIf { table.isExcluded() }.orEmpty()
- h2 {
+ div("project $excludedClass") {
id = table.id.toCoordinates()
- +"${table.id.toCoordinates()} (${table.fullDefinitionFilePath})"
- }
- if (table.isExcluded()) {
- h3 { +"Project is Excluded" }
- p { +"The project is excluded for the following reason(s):" }
- }
+ h2 {
+ +"${table.id.toCoordinates()} (${table.fullDefinitionFilePath})"
+ }
- table.pathExcludes.forEach { exclude ->
- p {
- div("reason") { +exclude.description }
+ if (table.isExcluded()) {
+ h3 { +"Project is Excluded" }
+ p { +"The project is excluded for the following reason(s):" }
}
- }
- table.vcs.let { vcsInfo ->
- h3(excludedClass) { +"VCS Information" }
+ table.pathExcludes.forEach { exclude ->
+ p {
+ div("reason") { +exclude.description }
+ }
+ }
- table("report-key-value-table $excludedClass") {
- tbody {
- tr {
- td { +"Type" }
- td { +vcsInfo.type.toString() }
- }
- tr {
- td { +"URL" }
- td { +vcsInfo.url }
- }
- tr {
- td { +"Path" }
- td { +vcsInfo.path }
- }
- tr {
- td { +"Revision" }
- td { +vcsInfo.revision }
+ table.vcs.let { vcsInfo ->
+ h3 { +"VCS Information" }
+
+ table("report-key-value-table $excludedClass") {
+ tbody {
+ tr {
+ td { +"Type" }
+ td { +vcsInfo.type.toString() }
+ }
+ tr {
+ td { +"URL" }
+ td { +vcsInfo.url }
+ }
+ tr {
+ td { +"Path" }
+ td { +vcsInfo.path }
+ }
+ tr {
+ td { +"Revision" }
+ td { +vcsInfo.revision }
+ }
}
}
}
- }
- h3(excludedClass) { +"Packages" }
+ h3 { +"Packages" }
- table("report-table report-project-table $excludedClass") {
- thead {
- tr {
- th { +"#" }
- th { +"Package" }
- th { +"Scopes" }
- th { +"Licenses" }
- th { +"Analyzer Issues" }
- th { +"Scanner Issues" }
+ table("report-table report-project-table $excludedClass") {
+ thead {
+ tr(excludedClass) {
+ th { +"#" }
+ th { +"Package" }
+ th { +"Scopes" }
+ th { +"Licenses" }
+ th { +"Open Issues" }
+ th { +"Excluded & Resolved Issues" }
+ }
}
- }
- tbody {
- repeat(table.rows.size) { index ->
- projectRow(table, index)
+ tbody {
+ repeat(table.rows.size) { index ->
+ projectRow(table, index)
+ }
}
}
}
}
private fun TBODY.projectRow(projectTable: ProjectTable, rowIndex: Int) {
- val row = projectTable.rows[rowIndex]
- val rowId = "${projectTable.id.toCoordinates()}-pkg-${rowIndex + 1}"
-
// Only mark the row as excluded if all scopes the dependency appears in are excluded.
- val rowExcludedClass = "excluded".takeIf { row.isExcluded() }.orEmpty()
-
- val cssClass = when {
- row.analyzerIssues.containsUnresolved() || row.scanIssues.containsUnresolved() -> "error"
- row.declaredLicenses.isEmpty() && row.detectedLicenses.isEmpty() -> "warning"
- else -> "success"
- }
+ val rowId = "${projectTable.id.toCoordinates()}-pkg-${rowIndex + 1}"
+ val row = projectTable.rows[rowIndex]
+ val rowExcludedClass = "excluded".takeIf { projectTable.isExcluded() || row.isExcluded() }.orEmpty()
- tr("$cssClass $rowExcludedClass") {
+ tr("pkg $rowExcludedClass") {
id = rowId
td {
a {
@@ -479,8 +475,8 @@ class StaticHtmlReporter : Reporter {
if (row.scopes.isNotEmpty()) {
ul {
row.scopes.forEach { scope ->
- val excludedClass = "excluded".takeIf { scope.isExcluded() }.orEmpty()
- li(excludedClass) {
+ val scopeExcludedClass = "excluded".takeIf { scope.isExcluded() }.orEmpty()
+ li("scope $scopeExcludedClass") {
+scope.name
if (scope.excludes.isNotEmpty()) {
+" "
@@ -528,7 +524,7 @@ class StaticHtmlReporter : Reporter {
}
if (!license.isDetectedExcluded) {
- div {
+ div("detected-license") {
licensesLink(license.license)
if (permalink != null) {
val count = license.locations.count { it.matchingPathExcludes.isEmpty() }
@@ -536,7 +532,7 @@ class StaticHtmlReporter : Reporter {
}
}
} else {
- div("excluded") {
+ div("detected-license excluded") {
+"${license.license} (Excluded: "
+pathExcludes.joinToString { it.description }
+")"
@@ -556,21 +552,39 @@ class StaticHtmlReporter : Reporter {
}
}
- td { issueList(row.analyzerIssues) }
+ td {
+ if (row.openIssues.isNotEmpty()) {
+ issueList(row.openIssues)
+ }
+ }
- td { issueList(row.scanIssues) }
+ td {
+ if (row.excludedOrResolvedIssues.isNotEmpty()) {
+ issueList(row.excludedOrResolvedIssues)
+ }
+ }
}
}
private fun TD.issueList(issues: List) {
- ul {
+ table("report-table package-issue-table") {
issues.forEach {
- li {
- p { issueDescription(it) }
+ val cssClass = when {
+ it.isResolved -> "resolved"
+ it.isExcluded -> "excluded"
+ it.severity == Severity.ERROR -> "error"
+ it.severity == Severity.WARNING -> "warning"
+ it.severity == Severity.HINT -> "hint"
+ else -> null
+ }
- if (it.isResolved) {
- classes = setOf("resolved")
- p { +it.resolutionDescription }
+ tr(cssClass) {
+ td {
+ p { issueDescription(it) }
+
+ if (it.isResolved) {
+ p { +it.resolutionDescription }
+ }
}
}
}
@@ -716,5 +730,3 @@ private fun IssueTable.title(): String =
private fun IssueTable.id(): String = "${type.name.lowercase()}-issue-summary"
private fun IssueTable.rowId(index: Int): String = "${id()}-$index"
-
-private fun Collection.containsUnresolved() = any { !it.isResolved }
diff --git a/plugins/reporters/static-html/src/main/kotlin/TablesReport.kt b/plugins/reporters/static-html/src/main/kotlin/TablesReport.kt
index 731aa6cfe1ba3..7724bbdca1645 100644
--- a/plugins/reporters/static-html/src/main/kotlin/TablesReport.kt
+++ b/plugins/reporters/static-html/src/main/kotlin/TablesReport.kt
@@ -176,12 +176,12 @@ internal data class ProjectTable(
/**
* All analyzer issues related to this package.
*/
- val analyzerIssues: List,
+ val openIssues: List,
/**
- * All scan issues related to this package.
+ * All issues which are either resolved or excluded or both.
*/
- val scanIssues: List
+ val excludedOrResolvedIssues: List
) {
/**
* Return true if and only if this [Row] is excluded by any [ScopeExclude]s
@@ -211,6 +211,7 @@ internal data class TablesReportIssue(
val source: String,
val description: String,
val resolutionDescription: String,
+ val isExcluded: Boolean,
val isResolved: Boolean,
val severity: Severity,
val howToFix: String
diff --git a/plugins/reporters/static-html/src/main/kotlin/TablesReportModelMapper.kt b/plugins/reporters/static-html/src/main/kotlin/TablesReportModelMapper.kt
index 5400498a7635e..24580e36dee1d 100644
--- a/plugins/reporters/static-html/src/main/kotlin/TablesReportModelMapper.kt
+++ b/plugins/reporters/static-html/src/main/kotlin/TablesReportModelMapper.kt
@@ -84,7 +84,8 @@ private fun OrtResult.getScopesForDependencies(project: Project): Map
+ val isRowExcluded = input.ortResult.isExcluded(id) ||
+ (id != project.id && scopesForId[id].orEmpty().all { it.value.isNotEmpty() })
+
+ issue.toTableReportIssue(
+ input.ortResult,
+ input.howToFixTextProvider,
+ isRowExcluded || input.ortResult.isExcluded(issue, id)
+ )
+ }
+
+ val (openIssues, excludedOrResolvedIssue) = issues.partition {
+ !(it.isResolved || it.isExcluded)
+ }
val scopes = scopesForId[id].orEmpty().map { (name, excludes) ->
ProjectTable.Scope(name, excludes)
@@ -161,12 +179,8 @@ private fun getProjectTable(input: ReporterInput, project: Project): ProjectTabl
declaredLicenses = declaredLicenses,
detectedLicenses = detectedLicenses,
effectiveLicense = effectiveLicense,
- analyzerIssues = analyzerIssues.map {
- it.toTableReportIssue(input.ortResult, input.howToFixTextProvider)
- },
- scanIssues = scannerIssuesForId[id].orEmpty().map {
- it.toTableReportIssue(input.ortResult, input.howToFixTextProvider)
- }
+ openIssues.sortedByDescending { it.severity },
+ excludedOrResolvedIssue.sortedByDescending { it.isResolved }
)
}
@@ -194,7 +208,7 @@ private fun getAdvisorIssueSummaryTable(input: ReporterInput): IssueTable =
private fun Map>.toIssueSummaryTable(type: IssueTable.Type, input: ReporterInput): IssueTable {
val rows = flatMap { (id, issues) ->
issues.map { issue ->
- val resolvableIssue = issue.toTableReportIssue(input.ortResult, input.howToFixTextProvider)
+ val resolvableIssue = issue.toTableReportIssue(input.ortResult, input.howToFixTextProvider, false)
IssueTable.Row(resolvableIssue, id)
}
}.sortedWith(compareByDescending { it.issue.severity }.thenBy { it.id })
diff --git a/plugins/reporters/static-html/src/main/resources/static-html-reporter.css b/plugins/reporters/static-html/src/main/resources/static-html-reporter.css
index dbb70cb699b63..a2f40c84d17ad 100644
--- a/plugins/reporters/static-html/src/main/resources/static-html-reporter.css
+++ b/plugins/reporters/static-html/src/main/resources/static-html-reporter.css
@@ -144,57 +144,70 @@ ul {
border-right: 1px solid rgba(34, 36, 38, .15);
}
-.report-table li.resolved {
- color: #2c662d;
-}
-
.report-table details {
overflow: scroll;
}
-.report-table tr.error {
+.report-table tr.error td {
background: #fff6f6;
color: #9f3a38;
}
-.report-table tr.warning {
+.report-table tr.warning td {
background: #fffaf3;
color: #573a08;
}
-.report-table tr.hint {
+.report-table tr.hint td {
background: #f7f5ff;
color: #1c0859;
}
/**
- * Rule violation table, which specializes .report-table.
+ * Project section.
*/
-.report-rule-violation-table tr.resolved {
- background: #fcfff5;
- color: #2c662d;
+.project.excluded {
+ h3, .report-key-value-table, .report-project-table > thead th {
+ filter: opacity(50%);
+ }
}
-/**
- * Project table, which specializes .report-table.
- */
+.report-project-table .pkg.excluded {
+ > td:nth-child(1), td:nth-child(2), td:nth-child(3), td:nth-child(4) {
+ filter: opacity(50%);
+ }
+}
-.report-project-table tr.error {
- color: black;
+.report-project-table .pkg:not(.excluded) {
+ .scope.excluded, .detected-license.excluded {
+ filter: opacity(50%);
+ }
}
-.report-project-table tr.error td:nth-child(5),
-.report-project-table tr.error td:nth-child(6) {
- color: #9f3a38;
+.report-project-table td:has(.package-issue-table) {
+ padding: 0px;
}
-/*
- * Excluded state.
- */
+.package-issue-table {
+ border-spacing: 10px;
+ padding: 0px;
+ margin: 0px;
+}
-.excluded {
- filter: opacity(50%);
+.package-issue-table td {
+ border-radius: .60rem !important;
+ border-bottom: 1px solid rgba(34, 36, 38, .15);
+}
+
+.package-issue-table tr.resolved td {
+ background: #fcfff5;
+ color: #2c662d;
+}
+
+.package-issue-table tr.excluded td {
+ background: #f9fafb;
+ color: rgba(34, 36, 38, .7);
}
.reason {
@@ -205,18 +218,6 @@ ul {
display: inline;
}
-table.excluded tr.excluded {
- filter: opacity(100%);
-}
-
-table tr.excluded td li.excluded {
- filter: opacity(100%);
-}
-
-table.excluded tr.excluded td li.excluded {
- filter: opacity(100%);
-}
-
/**
* Report table media specific styling.
*/