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 -

Gradle:org.ossreviewtoolkit:nested-fake-project:1.0.0 (sub/module/project/build.gradle)

-

Project is Excluded

-

The project is excluded for the following reason(s):

-

-

EXAMPLE_OF - The project is an example.
-

-

VCS Information

- - - - - - - - - - - - - - - - - - - -
TypeGit
URLhttps://example.com/git
Pathproject
Revisionmaster
-

Packages

- - - - - - - - - - - - - - - - - - - - - -
#PackageScopesLicensesAnalyzer IssuesScanner Issues
1Gradle:org.ossreviewtoolkit:nested-fake-project:1.0.0Declared 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.

      -
    • -
    -
    -

    Gradle:org.ossreviewtoolkit.gradle.example:lib:1.0.0 (analyzer/src/funTest/assets/projects/synthetic/gradle/lib/build.gradle)

    -

    VCS Information

    - - - - - - - - - - - - - - - - - - - -
    TypeGit
    URLhttps://github.com/oss-review-toolkit/ort.git
    Pathanalyzer/src/funTest/assets/projects/synthetic/gradle/lib
    Revisionmaster
    -

    Packages

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    #PackageScopesLicensesAnalyzer IssuesScanner Issues
    1Gradle:org.ossreviewtoolkit.gradle.example:lib:1.0.0Detected Licenses (from VCS):
    -
    -
    BSD-3-Clause (link to the location)
    - -
    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)
    -
    MIT (link to the location)
    -
    -
    Effective License:
    -
    -
    BSD-3-Clause AND GPL-2.0-only WITH Classpath-exception-2.0 AND LicenseRef-test-Apache-2.0-multi-line AND LicenseRef-test-Apache-2.0-single-line OR LicenseRef-test-Apache-2.0-multi-line AND LicenseRef-test-Apache-2.0-single-line AND MIT
    -
    -
    -
    -
      -
    • -

      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.

      -
    • -
    -
    2Ant:junit:junit:4.12 -
      -
    • testCompile -
      Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
      -
    • -
    -
    Declared Licenses:
    -
    - -
    -
    Effective License:
    -
    - -
    -
    -
    -
      -
    • -

      2024-04-25T07:44:20.725613974Z [WARNING]: Gradle - Example analyzer warning in excluded - package.

      -
    • -
    -
    -
      -
      3Maven:com.foobar:foobar:1.0 -
        -
      • testCompile -
        Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
        -
      • -
      -
      Concluded License:
      -
      - -
      -
      Declared Licenses:
      -
      - - -
      -
      Effective License:
      -
      - -
      -
      -
      -
        -
        -
          -
          4Maven:com.h2database:h2:1.4.200 -
            -
          • testCompile -
            Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
            -
          • -
          -
          Concluded License:
          -
          - -
          -
          Declared Licenses:
          -
          - - -
          -
          Effective License:
          -
          - -
          -
          -
          -
            -
            -
              -
              5Maven:org.apache.commons:commons-lang3:3.5 -
                -
              • compile
              • -
              • testCompile -
                Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
                -
              • -
              -
              Declared Licenses:
              -
              - -
              -
              Effective License:
              -
              - -
              -
              -
              -
                -
                -
                  -
                  6Maven:org.apache.commons:commons-text:1.1 -
                    -
                  • compile
                  • -
                  • testCompile -
                    Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
                    -
                  • -
                  -
                  Declared Licenses:
                  -
                  - -
                  -
                  Effective License:
                  -
                  - -
                  -
                  -
                  -
                    -
                  • -

                    2024-04-25T07:44:20.725613974Z [WARNING]: Gradle - Example analyzer warning in included - package.

                    -
                  • -
                  -
                  -
                    -
                    7Maven:org.example.test:component:1.11 -
                      -
                    • compile
                    • -
                    • testCompile -
                      Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
                      -
                    • -
                    -
                    -
                      -
                      -
                        -
                        8Maven:org.hamcrest:hamcrest-core:1.3 -
                          -
                        • testCompile -
                          Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
                          -
                        • -
                        -
                        Declared Licenses:
                        -
                        - -
                        -
                        Effective License:
                        -
                        - -
                        -
                        -
                        -
                          -
                          -
                            -
                            +
                            +

                            Gradle:org.ossreviewtoolkit:nested-fake-project:1.0.0 (sub/module/project/build.gradle)

                            +

                            Project is Excluded

                            +

                            The project is excluded for the following reason(s):

                            +

                            +

                            EXAMPLE_OF - The project is an example.
                            +

                            +

                            VCS Information

                            + + + + + + + + + + + + + + + + + + + +
                            TypeGit
                            URLhttps://example.com/git
                            Pathproject
                            Revisionmaster
                            +

                            Packages

                            + + + + + + + + + + + + + + + + + + + + + +
                            #PackageScopesLicensesOpen IssuesExcluded & Resolved Issues
                            1Gradle:org.ossreviewtoolkit:nested-fake-project:1.0.0Declared Licenses:
                            +
                            + + +
                            +
                            Detected Licenses (from VCS):
                            +
                            + +
                            MIT (Excluded: EXAMPLE_OF - These are example files.)
                            +
                            +
                            Effective License:
                            +
                            + +
                            +
                            +
                            + + + + + + + + + + + + + + + + +
                            +

                            2024-04-25T07:44:20.725613974Z [ERROR]: FakeScanner - Example error, resolved.

                            +

                            + Resolved by: CANT_FIX_ISSUE - Resolved for illustration.

                            +
                            +

                            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.

                            +
                            +
                            +
                            +
                            +

                            Gradle:org.ossreviewtoolkit.gradle.example:lib:1.0.0 (analyzer/src/funTest/assets/projects/synthetic/gradle/lib/build.gradle)

                            +

                            VCS Information

                            + + + + + + + + + + + + + + + + + + + +
                            TypeGit
                            URLhttps://github.com/oss-review-toolkit/ort.git
                            Pathanalyzer/src/funTest/assets/projects/synthetic/gradle/lib
                            Revisionmaster
                            +

                            Packages

                            + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                            #PackageScopesLicensesOpen IssuesExcluded & Resolved Issues
                            1Gradle:org.ossreviewtoolkit.gradle.example:lib:1.0.0Detected Licenses (from VCS):
                            +
                            +
                            BSD-3-Clause (link to the location)
                            + +
                            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)
                            +
                            MIT (link to the location)
                            +
                            +
                            Effective License:
                            +
                            +
                            BSD-3-Clause AND GPL-2.0-only WITH Classpath-exception-2.0 AND LicenseRef-test-Apache-2.0-multi-line AND LicenseRef-test-Apache-2.0-single-line OR LicenseRef-test-Apache-2.0-multi-line AND LicenseRef-test-Apache-2.0-single-line AND MIT
                            +
                            +
                            +
                            + + + + + + + + + + + + + + + + + + + + + + + + + +
                            +

                            2024-04-25T07:44:20.725613974Z [ERROR]: Gradle - Example error.

                            +
                            +

                            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 [ERROR]: FakeScanner - Example error.

                            +
                            +

                            2024-04-25T07:44:20.725613974Z [WARNING]: Gradle - Example warning.

                            +
                            +

                            2024-04-25T07:44:20.725613974Z [WARNING]: FakeScanner - Example warning.

                            +
                            +

                            2024-04-25T07:44:20.725613974Z [HINT]: Gradle - Example hint.

                            +
                            +

                            2024-04-25T07:44:20.725613974Z [HINT]: FakeScanner - Example hint.

                            +
                            +
                            + + + + + + + +
                            +

                            2024-04-25T07:44:20.725613974Z [ERROR]: Gradle - Example error, resolved.

                            +

                            + Resolved by: CANT_FIX_ISSUE - Resolved for illustration.

                            +
                            +

                            2024-04-25T07:44:20.725613974Z [ERROR]: FakeScanner - Example error, resolved.

                            +

                            + Resolved by: CANT_FIX_ISSUE - Resolved for illustration.

                            +
                            +
                            2Ant:junit:junit:4.12 +
                              +
                            • testCompile +
                              Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
                              +
                            • +
                            +
                            Declared Licenses:
                            +
                            + +
                            +
                            Effective License:
                            +
                            + +
                            +
                            +
                            + + + + +
                            +

                            2024-04-25T07:44:20.725613974Z [WARNING]: Gradle - Example analyzer warning in excluded + package.

                            +
                            +
                            3Maven:com.foobar:foobar:1.0 +
                              +
                            • testCompile +
                              Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
                              +
                            • +
                            +
                            Concluded License:
                            +
                            + +
                            +
                            Declared Licenses:
                            +
                            + + +
                            +
                            Effective License:
                            +
                            + +
                            +
                            +
                            4Maven:com.h2database:h2:1.4.200 +
                              +
                            • testCompile +
                              Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
                              +
                            • +
                            +
                            Concluded License:
                            +
                            + +
                            +
                            Declared Licenses:
                            +
                            + + +
                            +
                            Effective License:
                            +
                            + +
                            +
                            +
                            5Maven:org.apache.commons:commons-lang3:3.5 +
                              +
                            • compile
                            • +
                            • testCompile +
                              Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
                              +
                            • +
                            +
                            Declared Licenses:
                            +
                            + +
                            +
                            Effective License:
                            +
                            + +
                            +
                            +
                            6Maven:org.apache.commons:commons-text:1.1 +
                              +
                            • compile
                            • +
                            • testCompile +
                              Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
                              +
                            • +
                            +
                            Declared Licenses:
                            +
                            + +
                            +
                            Effective License:
                            +
                            + +
                            +
                            +
                            + + + + +
                            +

                            2024-04-25T07:44:20.725613974Z [WARNING]: Gradle - Example analyzer warning in included + package.

                            +
                            +
                            7Maven:org.example.test:component:1.11 +
                              +
                            • compile
                            • +
                            • testCompile +
                              Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
                              +
                            • +
                            +
                            8Maven:org.hamcrest:hamcrest-core:1.3 +
                              +
                            • testCompile +
                              Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
                              +
                            • +
                            +
                            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.
                              */