Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static HTML report model clean-ups #8606

Merged
merged 9 commits into from
May 3, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ import org.ossreviewtoolkit.model.config.RepositoryConfiguration
import org.ossreviewtoolkit.model.config.ScopeExclude
import org.ossreviewtoolkit.model.licenses.ResolvedLicenseLocation
import org.ossreviewtoolkit.model.yamlMapper
import org.ossreviewtoolkit.plugins.reporters.statichtml.ReportTableModel.IssueTable
import org.ossreviewtoolkit.plugins.reporters.statichtml.ReportTableModel.ProjectTable
import org.ossreviewtoolkit.plugins.reporters.statichtml.ReportTableModel.ResolvableIssue
import org.ossreviewtoolkit.reporter.Reporter
import org.ossreviewtoolkit.reporter.ReporterInput
import org.ossreviewtoolkit.utils.common.isValidUri
Expand All @@ -75,9 +72,9 @@ class StaticHtmlReporter : Reporter {
private val licensesSha1 = mutableMapOf<String, String>()

override fun generateReport(input: ReporterInput, outputDir: File, config: PluginConfiguration): List<File> {
val reportTableModel = ReportTableModelMapper.map(input)
val tablesReport = TablesReportModelMapper.map(input)

val html = renderHtml(reportTableModel)
val html = renderHtml(tablesReport)
val outputFile = outputDir.resolve(reportFilename)

outputFile.bufferedWriter().use {
Expand All @@ -87,7 +84,7 @@ class StaticHtmlReporter : Reporter {
return listOf(outputFile)
}

private fun renderHtml(reportTableModel: ReportTableModel): String {
private fun renderHtml(tablesReport: TablesReport): String {
val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()

document.append.html {
Expand Down Expand Up @@ -138,46 +135,46 @@ class StaticHtmlReporter : Reporter {
h2 { +"Project" }

div {
with(reportTableModel.vcsInfo) {
with(tablesReport.vcsInfo) {
+"Scanned revision $revision of $type repository $url"
}
}

if (reportTableModel.labels.isNotEmpty()) {
labelsTable(reportTableModel.labels)
if (tablesReport.labels.isNotEmpty()) {
labelsTable(tablesReport.labels)
}

index(reportTableModel)
index(tablesReport)

reportTableModel.ruleViolations?.let {
tablesReport.ruleViolations?.let {
ruleViolationTable(it)
}

if (reportTableModel.analyzerIssueSummary.rows.isNotEmpty()) {
issueTable(reportTableModel.analyzerIssueSummary)
if (tablesReport.analyzerIssueSummary.rows.isNotEmpty()) {
issueTable(tablesReport.analyzerIssueSummary)
}

if (reportTableModel.scannerIssueSummary.rows.isNotEmpty()) {
issueTable(reportTableModel.scannerIssueSummary)
if (tablesReport.scannerIssueSummary.rows.isNotEmpty()) {
issueTable(tablesReport.scannerIssueSummary)
}

if (reportTableModel.advisorIssueSummary.rows.isNotEmpty()) {
issueTable(reportTableModel.advisorIssueSummary)
if (tablesReport.advisorIssueSummary.rows.isNotEmpty()) {
issueTable(tablesReport.advisorIssueSummary)
}

reportTableModel.projectDependencies.forEach { (project, table) ->
tablesReport.projectDependencies.forEach { (project, table) ->
projectTable(project, table)
}

repositoryConfiguration(reportTableModel.config)
repositoryConfiguration(tablesReport.config)
}
}
}

return document.serialize().normalizeLineBreaks()
}

private fun getRuleViolationSummaryString(ruleViolations: List<ReportTableModel.ResolvableViolation>): String {
private fun getRuleViolationSummaryString(ruleViolations: List<TablesReportViolation>): String {
val violations = ruleViolations.filterNot { it.isResolved }.groupBy { it.violation.severity }
val errorCount = violations[Severity.ERROR].orEmpty().size
val warningCount = violations[Severity.WARNING].orEmpty().size
Expand All @@ -200,43 +197,43 @@ class StaticHtmlReporter : Reporter {
}
}

private fun DIV.index(reportTableModel: ReportTableModel) {
private fun DIV.index(tablesReport: TablesReport) {
h2 { +"Index" }

ul {
reportTableModel.ruleViolations?.let { ruleViolations ->
tablesReport.ruleViolations?.let { ruleViolations ->
li {
a("#$RULE_VIOLATION_TABLE_ID") {
+getRuleViolationSummaryString(ruleViolations)
}
}
}

if (reportTableModel.analyzerIssueSummary.rows.isNotEmpty()) {
if (tablesReport.analyzerIssueSummary.rows.isNotEmpty()) {
li {
a("#${reportTableModel.analyzerIssueSummary.id()}") {
+reportTableModel.analyzerIssueSummary.title()
a("#${tablesReport.analyzerIssueSummary.id()}") {
+tablesReport.analyzerIssueSummary.title()
}
}
}

if (reportTableModel.scannerIssueSummary.rows.isNotEmpty()) {
if (tablesReport.scannerIssueSummary.rows.isNotEmpty()) {
li {
a("#${reportTableModel.scannerIssueSummary.id()}") {
+reportTableModel.scannerIssueSummary.title()
a("#${tablesReport.scannerIssueSummary.id()}") {
+tablesReport.scannerIssueSummary.title()
}
}
}

if (reportTableModel.advisorIssueSummary.rows.isNotEmpty()) {
if (tablesReport.advisorIssueSummary.rows.isNotEmpty()) {
li {
a("#${reportTableModel.advisorIssueSummary.id()}") {
+reportTableModel.advisorIssueSummary.title()
a("#${tablesReport.advisorIssueSummary.id()}") {
+tablesReport.advisorIssueSummary.title()
}
}
}

reportTableModel.projectDependencies.forEach { (project, projectTable) ->
tablesReport.projectDependencies.forEach { (project, projectTable) ->
li {
a("#${project.id.toCoordinates()}") {
+project.id.toCoordinates()
Expand All @@ -259,7 +256,7 @@ class StaticHtmlReporter : Reporter {
}
}

private fun DIV.ruleViolationTable(ruleViolations: List<ReportTableModel.ResolvableViolation>) {
private fun DIV.ruleViolationTable(ruleViolations: List<TablesReportViolation>) {
h2 {
id = RULE_VIOLATION_TABLE_ID
+getRuleViolationSummaryString(ruleViolations)
Expand Down Expand Up @@ -288,7 +285,7 @@ class StaticHtmlReporter : Reporter {
}
}

private fun TBODY.ruleViolationRow(rowIndex: Int, ruleViolation: ReportTableModel.ResolvableViolation) {
private fun TBODY.ruleViolationRow(rowIndex: Int, ruleViolation: TablesReportViolation) {
val cssClass = if (ruleViolation.isResolved) {
"ort-resolved"
} else {
Expand Down Expand Up @@ -357,7 +354,7 @@ class StaticHtmlReporter : Reporter {
}
}

private fun TBODY.issueRow(rowId: String, rowIndex: Int, row: ReportTableModel.IssueRow) {
private fun TBODY.issueRow(rowId: String, rowIndex: Int, row: IssueTable.Row) {
val cssClass = when (row.issue.severity) {
Severity.ERROR -> "ort-error"
Severity.WARNING -> "ort-warning"
Expand Down Expand Up @@ -457,7 +454,7 @@ class StaticHtmlReporter : Reporter {
}
}

private fun TBODY.projectRow(projectId: String, rowIndex: Int, row: ReportTableModel.DependencyRow) {
private fun TBODY.projectRow(projectId: String, rowIndex: Int, row: ProjectTable.Row) {
// Only mark the row as excluded if all scopes the dependency appears in are excluded.
val rowExcludedClass =
if (row.scopes.isNotEmpty() && row.scopes.all { it.value.isNotEmpty() }) "ort-excluded" else ""
Expand Down Expand Up @@ -568,7 +565,7 @@ class StaticHtmlReporter : Reporter {
}
}

private fun TD.issueList(issues: List<ResolvableIssue>) {
private fun TD.issueList(issues: List<TablesReportIssue>) {
ul {
issues.forEach {
li {
Expand All @@ -583,7 +580,7 @@ class StaticHtmlReporter : Reporter {
}
}

private fun P.issueDescription(issue: ResolvableIssue) {
private fun P.issueDescription(issue: TablesReportIssue) {
var first = true
issue.description.lines().forEach {
if (first) first = false else br
Expand Down Expand Up @@ -726,3 +723,5 @@ 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<TablesReportIssue>.containsUnresolved() = any { !it.isResolved }
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ import org.ossreviewtoolkit.model.config.ScopeExclude
import org.ossreviewtoolkit.model.licenses.ResolvedLicense
import org.ossreviewtoolkit.utils.spdx.SpdxExpression

internal fun Collection<ReportTableModel.ResolvableIssue>.containsUnresolved() = any { !it.isResolved }

internal data class ReportTableModel(
internal data class TablesReport(
/**
* The [VcsInfo] for the scanned project.
*/
Expand All @@ -51,20 +49,20 @@ internal data class ReportTableModel(
/**
* A list containing all evaluator rule violations. `null` if no evaluator result is available.
*/
val ruleViolations: List<ResolvableViolation>?,
val ruleViolations: List<TablesReportViolation>?,

/**
* An [IssueTable] containing all dependencies that caused issues.
* An [IssueTable] containing all analyzer issues.
*/
val analyzerIssueSummary: IssueTable,

/**
* A [IssueTable] containing all scanner issues.
* An [IssueTable] containing all scanner issues.
*/
val scannerIssueSummary: IssueTable,

/**
* A [IssueTable] containing all advisor issues.
* An [IssueTable] containing all advisor issues.
*/
val advisorIssueSummary: IssueTable,

Expand All @@ -77,28 +75,55 @@ internal data class ReportTableModel(
* The labels from [OrtResult.labels].
*/
val labels: Map<String, String>
)

internal data class IssueTable(
val type: Type,
val rows: List<Row>
) {
data class ProjectTable(
/**
* The dependencies of this project.
*/
val rows: List<DependencyRow>,
val errorCount = rows.count { it.issue.severity == Severity.ERROR }
val warningCount = rows.count { it.issue.severity == Severity.WARNING }
val hintCount = rows.count { it.issue.severity == Severity.HINT }

enum class Type {
ANALYZER,
SCANNER,
ADVISOR
}

data class Row(
/**
* The path to the directory containing the definition file of the project, relative to the analyzer root,
* see [OrtResult.getDefinitionFilePathRelativeToAnalyzerRoot].
* The issue of this row represents of the given [type][type].
*/
val fullDefinitionFilePath: String,
val issue: TablesReportIssue,

/**
* Information about if and why the project is excluded by [PathExclude]s.
* The identifier of the package the issue corresponds to.
*/
val pathExcludes: List<PathExclude>
) {
fun isExcluded() = pathExcludes.isNotEmpty()
}
val id: Identifier
)
}

internal data class ProjectTable(
/**
* The dependencies of this project.
*/
val rows: List<Row>,

/**
* The path to the directory containing the definition file of the project, relative to the analyzer root,
* see [OrtResult.getDefinitionFilePathRelativeToAnalyzerRoot].
*/
val fullDefinitionFilePath: String,

/**
* Information about if and why the project is excluded by [PathExclude]s.
*/
val pathExcludes: List<PathExclude>
) {
fun isExcluded() = pathExcludes.isNotEmpty()

data class DependencyRow(
data class Row(
/**
* The identifier of the package.
*/
Expand Down Expand Up @@ -143,53 +168,26 @@ internal data class ReportTableModel(
/**
* All analyzer issues related to this package.
*/
val analyzerIssues: List<ResolvableIssue>,
val analyzerIssues: List<TablesReportIssue>,

/**
* All scan issues related to this package.
*/
val scanIssues: List<ResolvableIssue>
)

data class IssueTable(
val type: Type,
val rows: List<IssueRow>
) {
val errorCount = rows.count { it.issue.severity == Severity.ERROR }
val warningCount = rows.count { it.issue.severity == Severity.WARNING }
val hintCount = rows.count { it.issue.severity == Severity.HINT }

enum class Type {
ANALYZER,
SCANNER,
ADVISOR
}
}

data class IssueRow(
/**
* All analyzer issues related to this package, grouped by the [Identifier] of the [Project] they appear in.
*/
val issue: ResolvableIssue,

/**
* The identifier of the package the issue corresponds to.
*/
val id: Identifier
)

data class ResolvableIssue(
val source: String,
val description: String,
val resolutionDescription: String,
val isResolved: Boolean,
val severity: Severity,
val howToFix: String
)

data class ResolvableViolation(
val violation: RuleViolation,
val resolutionDescription: String,
val isResolved: Boolean
val scanIssues: List<TablesReportIssue>
)
}

internal data class TablesReportIssue(
val source: String,
val description: String,
val resolutionDescription: String,
val isResolved: Boolean,
val severity: Severity,
val howToFix: String
)

internal data class TablesReportViolation(
val violation: RuleViolation,
val resolutionDescription: String,
val isResolved: Boolean
)
Loading
Loading