From e57157fdcb636d9388596511d64d81f01889a84a Mon Sep 17 00:00:00 2001 From: rhartuv Date: Sun, 19 Jul 2026 12:09:57 +0300 Subject: [PATCH 1/2] fix: refactor finding column logic for excluded components --- openspec/specs/report-page/spec.md | 2 +- openspec/specs/reports-table/spec.md | 26 +++++++---- src/main/webui/src/components/Finding.tsx | 12 +++-- src/main/webui/src/components/SbomsTable.tsx | 10 ++-- src/main/webui/src/utils/findingDisplay.ts | 24 +++++----- .../exploitiq/rest/GetProductsRestTest.java | 18 ++++++++ .../devservices/products/product-10.json | 37 +++++++++++++-- .../reports/test-sbom-report-10-report-1.json | 45 ------------------ .../reports/test-sbom-report-10-report-2.json | 46 ------------------- 9 files changed, 99 insertions(+), 121 deletions(-) delete mode 100644 src/test/resources/devservices/reports/test-sbom-report-10-report-1.json delete mode 100644 src/test/resources/devservices/reports/test-sbom-report-10-report-2.json diff --git a/openspec/specs/report-page/spec.md b/openspec/specs/report-page/spec.md index 2ce285cd..a9ed1f77 100644 --- a/openspec/specs/report-page/spec.md +++ b/openspec/specs/report-page/spec.md @@ -138,7 +138,7 @@ The report page SHALL display an embedded repository reports table that conforms ### Requirement: Report Page Layout The report page SHALL use PatternFly layout components and follow the standard page structure. The report page SHALL display a breadcrumb navigation, page title, and **product analysis status** at the top of the page. The product analysis status SHALL appear on its own row **directly below** the page title (not inline beside the title). -The product analysis status SHALL be exactly one of two labels: **In progress** or **Completed**. It SHALL NOT display vulnerability findings (Vulnerable, Uncertain, Not vulnerable, Excluded, Failed)—those remain in the Finding column on the reports table and in per-repository rows. +The product analysis status SHALL be exactly one of two labels: **In progress** or **Completed**. It SHALL NOT display vulnerability findings (Vulnerable, Uncertain, Not vulnerable, No components analyzed, Failed)—those remain in the Finding column on the reports table and in per-repository rows. Status SHALL be derived from backend-computed `summary.productState` on `ProductSummary` (same rules as `ReportRepositoryService` product summary aggregation): diff --git a/openspec/specs/reports-table/spec.md b/openspec/specs/reports-table/spec.md index ea1e173d..38b0409d 100644 --- a/openspec/specs/reports-table/spec.md +++ b/openspec/specs/reports-table/spec.md @@ -125,23 +125,33 @@ When a product has exactly one submitted report (`submittedCount === 1`), clicki - **AND** only the clicked row shows the loading indicator (other rows remain unchanged) ### Requirement: Reports Table Finding Column -The reports table SHALL display a "Finding" column with one finding per product. If any component report is still in pending, queued, or sent state, the Finding SHALL be "In progress" until every component has a terminal outcome (completed, failed, expired, or excluded). After that, six outcomes apply in priority order: Vulnerable > Uncertain > Failed > Excluded > Not vulnerable. Only the single applicable finding is shown per row. Counts for Vulnerable, Uncertain, and Excluded only; no count for In progress, Not vulnerable, or Failed. +The reports table SHALL display a "Finding" column with one finding per product. If any component report is still in pending, queued, or sent state, the Finding SHALL be "In progress" until every component has a terminal outcome (completed, failed, expired, or excluded). After that, outcomes apply in priority order: Vulnerable > Uncertain > Failed > Not vulnerable > No components analyzed. Only the single applicable finding is shown per row. Counts for Vulnerable and Uncertain only; no count for In progress, Not vulnerable, Failed, or No components analyzed. + +The legacy Finding label **Excluded** (including count forms such as "2 Excluded") SHALL NOT be shown in the Finding column. #### Scenario: Finding column header - **WHEN** a user views the reports table - **THEN** the column header is "Finding" with a help icon and popover explaining priority and states +- **AND** the popover states that while any repository is pending, queued, or sent the finding is In progress; after that the highest-priority analyzed outcome is shown; Not vulnerable applies when analyzed components are not vulnerable even if some components were excluded; No components analyzed applies when every submitted component was excluded #### Scenario: Finding by priority - **WHEN** a product has any report in pending, queued, or sent state → display "In progress", no count, outlined grey label and InProgressIcon (even if some other repositories already have a vulnerable or uncertain result) - **WHEN** no reports are pending, queued, or sent, and the product has one or more vulnerable repositories → display "Vulnerable" + count, red (danger) label - **WHEN** no reports are pending, queued, or sent, zero vulnerable, and one or more uncertain → display "Uncertain" + count, orange (warning) label -- **WHEN** no reports are pending, queued, or sent, zero vulnerable, zero uncertain, no failed/expired reports, and one or more excluded components (`statusCounts["excluded"]` > 0) → display "Excluded" + count, with label styling as defined (e.g. grey or info) -- **WHEN** no reports are pending, queued, or sent, 100% of reports are completed and 100% are not vulnerable and no excluded components → display "Not vulnerable", no count, green (success) label - -#### Scenario: Excluded state uses submission failure count -- **WHEN** a product has excluded components (submissionFailures length > 0) and no higher-priority finding -- **THEN** the Finding column displays "Excluded" with the count equal to `statusCounts["excluded"]` (length of product submissionFailures) -- **AND** the count is shown in the same format as Vulnerable and Uncertain (e.g. "3 Excluded") +- **WHEN** no reports are pending, queued, or sent, zero vulnerable, zero uncertain, and one or more failed/expired reports → display "Failed", no count, grey filled label with ExclamationCircleIcon +- **WHEN** no reports are pending, queued, or sent, zero vulnerable, zero uncertain, no failed/expired reports, and one or more analyzed components are not vulnerable (even if `statusCounts["excluded"]` > 0 but not 100% of submitted components) → display "Not vulnerable", no count, green (success) label +- **WHEN** no reports are pending, queued, or sent, zero vulnerable, zero uncertain, no failed/expired reports, and 100% of submitted components are excluded (`statusCounts["excluded"]` equals `submittedCount`) → display "No components analyzed", no count, purple filled PatternFly Label (default size/state; no icon) + +#### Scenario: Mixed excluded and analyzed components use analyzed finding +- **WHEN** a product has one or more excluded components and one or more successfully analyzed components with an active finding status (Vulnerable, Uncertain, or Not vulnerable) +- **THEN** the Finding column displays the analyzed outcome according to priority Vulnerable > Uncertain > Not vulnerable +- **AND** the Finding column does not display "Excluded" or any excluded count + +#### Scenario: All components excluded shows No components analyzed +- **WHEN** a product has `submittedCount` equal to the excluded count (`statusCounts["excluded"]`, from `submissionFailures.length`) and no higher-priority finding applies +- **THEN** the Finding column displays "No components analyzed" +- **AND** the label uses PatternFly `color="purple"` and `variant="filled"` with no icon and no numeric count +- **AND** the Finding column does not display "Excluded" or an excluded count ### Requirement: Reports Page Tabs and Single Repositories diff --git a/src/main/webui/src/components/Finding.tsx b/src/main/webui/src/components/Finding.tsx index 2243dc49..56d5e5ca 100644 --- a/src/main/webui/src/components/Finding.tsx +++ b/src/main/webui/src/components/Finding.tsx @@ -37,6 +37,12 @@ export const FailedStatus: React.FC = () => ( ); +export const NoComponentsAnalyzedStatus: React.FC = () => ( + +); + const Finding: React.FC = ({ finding }) => { if (!finding) return null; @@ -56,10 +62,8 @@ const Finding: React.FC = ({ finding }) => { label = "Not vulnerable"; color = apiToColor(JUSTIFICATION_API.NOT_VULNERABLE); break; - case "excluded": - label = "Excluded"; - color = "grey"; - break; + case "no-components-analyzed": + return ; case "uncertain": label = "Uncertain"; color = apiToColor(JUSTIFICATION_API.UNCERTAIN); diff --git a/src/main/webui/src/components/SbomsTable.tsx b/src/main/webui/src/components/SbomsTable.tsx index d6c8c921..11ebcc9b 100644 --- a/src/main/webui/src/components/SbomsTable.tsx +++ b/src/main/webui/src/components/SbomsTable.tsx @@ -219,9 +219,13 @@ const SbomsTable: React.FC = () => { While any repository is still pending, queued, or sent, the finding is In progress. After every repository has finished, this shows the highest risk - level detected across all of them. Not Vulnerable - appears only when every repository is analyzed and - found to be not vulnerable. + level detected across analyzed components (Vulnerable, + Uncertain, Failed, then Not vulnerable). Not + Vulnerable appears only when every analyzed + repository is found to be not vulnerable. Excluded + components do not override an analyzed finding. When + every submitted component was excluded, the finding is + No components analyzed. } > diff --git a/src/main/webui/src/utils/findingDisplay.ts b/src/main/webui/src/utils/findingDisplay.ts index c7cf511c..8fca2327 100644 --- a/src/main/webui/src/utils/findingDisplay.ts +++ b/src/main/webui/src/utils/findingDisplay.ts @@ -25,7 +25,7 @@ export type Finding = | { type: "uncertain"; count?: number } | { type: "in-progress" } | { type: "failed" } - | { type: "excluded"; count?: number }; + | { type: "no-components-analyzed" }; export type ProductStatus = { vulnerableCount: number; @@ -75,14 +75,15 @@ export function getProductAnalysisStatus( /** * Returns the single prioritized finding for a product row (reports table). * While any repository is still pending, queued, or sent, the row shows In progress only; - * after all have a terminal outcome, priority is: Vulnerable > Uncertain > Failed > Excluded > Not vulnerable. + * after all have a terminal outcome, priority is: + * Vulnerable > Uncertain > Failed > Not vulnerable > No components analyzed. * Only returns type + optional count; color/variant are applied by Finding component. - * totalCount uses submittedCount (e.g. productSummary.data.submittedCount) when provided. - * Excluded uses statusCounts["excluded"] (submission failure count from API). + * Not vulnerable applies when any analyzed component is not vulnerable (exclusions do not block it). + * No components analyzed applies when statusCounts["excluded"] equals submittedCount (100% excluded). */ export function getProductFinding( productStatus: ProductStatus, - analysisState: string, + _analysisState: string, statusCounts: Record, submittedCount?: number, ): Finding | null { @@ -104,15 +105,16 @@ export function getProductFinding( if (hasFailedInCounts(statusCounts)) { return { type: "failed" }; } - const excludedCount = getExcludedCount(statusCounts); - if (excludedCount > 0) { - return { type: "excluded", count: excludedCount }; + if (productStatus.notVulnerableCount > 0) { + return { type: "not-vulnerable" }; } + const excludedCount = getExcludedCount(statusCounts); if ( - analysisState === "completed" && - productStatus.notVulnerableCount === submittedCount + submittedCount !== undefined && + submittedCount > 0 && + excludedCount === submittedCount ) { - return { type: "not-vulnerable" }; + return { type: "no-components-analyzed" }; } return null; } diff --git a/src/test/java/com/redhat/ecosystemappeng/exploitiq/rest/GetProductsRestTest.java b/src/test/java/com/redhat/ecosystemappeng/exploitiq/rest/GetProductsRestTest.java index a3b5afb7..a88e49c2 100644 --- a/src/test/java/com/redhat/ecosystemappeng/exploitiq/rest/GetProductsRestTest.java +++ b/src/test/java/com/redhat/ecosystemappeng/exploitiq/rest/GetProductsRestTest.java @@ -135,6 +135,24 @@ void testGetSbomReportById_ReturnsExpectedStructure() { .body("summary.justificationStatusCounts", equalTo(java.util.Map.of("FALSE", 1, "TRUE", 1))); } + @Test + void testGetSbomReportById_AllExcludedProduct_ReturnsExcludedStatusCount() { + String productId = "product-10"; + + RestAssured.given() + .when() + .get("/api/v1/reports/product/{id}", productId) + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("data.id", equalTo(productId)) + .body("data.name", equalTo("test-sbom-product-10")) + .body("data.submittedCount", equalTo(5)) + .body("data.submissionFailures", hasSize(5)) + .body("summary.statusCounts.excluded", equalTo(5)) + .body("summary.productState", equalTo("completed")); + } + @Test void testGetSbomReportById_NotFound() { RestAssured.given() diff --git a/src/test/resources/devservices/products/product-10.json b/src/test/resources/devservices/products/product-10.json index 08de8c84..8fce0ac2 100644 --- a/src/test/resources/devservices/products/product-10.json +++ b/src/test/resources/devservices/products/product-10.json @@ -4,8 +4,39 @@ "version": "1.0.0", "cveId": "CVE-2024-99999", "submittedAt": "2025-02-10T10:00:00.000000Z", - "submittedCount": 2, + "submittedCount": 5, + "completedAt": "2025-02-10T10:30:00.000000Z", "metadata": {}, - "submissionFailures": [] + "submissionFailures": [ + { + "name": "excluded-component-1", + "version": "1.0.0", + "image": "pkg:oci/excluded-component-1@sha256:aaa111", + "error": "Component excluded from analysis due to unsupported package type" + }, + { + "name": "excluded-component-2", + "version": "1.0.0", + "image": "pkg:oci/excluded-component-2@sha256:aaa222", + "error": "Component excluded from analysis due to unsupported package type" + }, + { + "name": "excluded-component-3", + "version": "1.0.0", + "image": "pkg:oci/excluded-component-3@sha256:aaa333", + "error": "Component excluded from analysis due to unsupported package type" + }, + { + "name": "excluded-component-4", + "version": "1.0.0", + "image": "pkg:oci/excluded-component-4@sha256:aaa444", + "error": "Component excluded from analysis due to unsupported package type" + }, + { + "name": "excluded-component-5", + "version": "1.0.0", + "image": "pkg:oci/excluded-component-5@sha256:aaa555", + "error": "Component excluded from analysis due to unsupported package type" + } + ] } - diff --git a/src/test/resources/devservices/reports/test-sbom-report-10-report-1.json b/src/test/resources/devservices/reports/test-sbom-report-10-report-1.json deleted file mode 100644 index 8ef6ac50..00000000 --- a/src/test/resources/devservices/reports/test-sbom-report-10-report-1.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "_id": { - "$oid": "507f1f77bcf86cd799439021" - }, - "input": { - "scan": { - "id": "x1y2z3a4-b5c6-4d7e-8f9a-0b1c2d3e4f5a", - "type": null, - "started_at": "2025-02-10T10:00:00.000000", - "vulns": [ - { - "vuln_id": "CVE-2024-99999", - "description": null, - "score": null, - "severity": null, - "published_date": null, - "last_modified_date": null, - "url": null, - "feed_group": null, - "package": null, - "package_version": null, - "package_name": null, - "package_type": null - } - ] - }, - "image": { - "name": "test-image-10", - "tag": "v1.0.0", - "source_info": [ - { - "type": "code", - "git_repo": "https://github.com/example/test-product-10", - "ref": "main" - } - ] - } - }, - "metadata": { - "product_id": "product-10", - "submitted_at": "2025-02-10T10:00:00.000000Z", - "user": "test@example.com" - }, - "info": {} -} \ No newline at end of file diff --git a/src/test/resources/devservices/reports/test-sbom-report-10-report-2.json b/src/test/resources/devservices/reports/test-sbom-report-10-report-2.json deleted file mode 100644 index db394208..00000000 --- a/src/test/resources/devservices/reports/test-sbom-report-10-report-2.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "_id": { - "$oid": "507f1f77bcf86cd799439022" - }, - "input": { - "scan": { - "id": "y2z3a4b5-c6d7-4e8f-9a0b-1c2d3e4f5a6b", - "type": null, - "started_at": "2025-02-10T10:05:00.000000", - "vulns": [ - { - "vuln_id": "CVE-2024-99999", - "description": null, - "score": null, - "severity": null, - "published_date": null, - "last_modified_date": null, - "url": null, - "feed_group": null, - "package": null, - "package_version": null, - "package_name": null, - "package_type": null - } - ] - }, - "image": { - "name": "test-image-10", - "tag": "v1.0.1", - "source_info": [ - { - "type": "code", - "git_repo": "https://github.com/example/test-product-10", - "ref": "main" - } - ] - } - }, - "metadata": { - "product_id": "product-10", - "submitted_at": "2025-02-10T10:00:00.000000Z", - "sent_at": "2025-02-10T10:05:00.000000Z", - "user": "test@example.com" - }, - "info": {} -} \ No newline at end of file From 37477561f11c2c3f41f13fd24451d65a2f5459c5 Mon Sep 17 00:00:00 2001 From: rhartuv Date: Mon, 20 Jul 2026 16:41:04 +0300 Subject: [PATCH 2/2] fix: removed the unused analysisState parameter --- src/main/webui/src/hooks/useReportsTableData.ts | 2 -- src/main/webui/src/utils/findingDisplay.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/src/main/webui/src/hooks/useReportsTableData.ts b/src/main/webui/src/hooks/useReportsTableData.ts index 6bf732a0..b85791cf 100644 --- a/src/main/webui/src/hooks/useReportsTableData.ts +++ b/src/main/webui/src/hooks/useReportsTableData.ts @@ -95,7 +95,6 @@ export function transformProductSummaryToRow(productSummary: ProductSummary): Re const completedAt = product.completedAt || ""; const statusCounts = summary.statusCounts || {}; - const analysisState = summary.productState; // Repositories analyzed from shared utility (completed + data.submittedCount, "analyzed" suffix) const { getDisplay } = getRepositoriesAnalyzedFromProduct(productSummary); @@ -119,7 +118,6 @@ export function transformProductSummaryToRow(productSummary: ProductSummary): Re const finding = getProductFinding( productStatus, - analysisState, statusCounts, product.submittedCount ); diff --git a/src/main/webui/src/utils/findingDisplay.ts b/src/main/webui/src/utils/findingDisplay.ts index 8fca2327..96233235 100644 --- a/src/main/webui/src/utils/findingDisplay.ts +++ b/src/main/webui/src/utils/findingDisplay.ts @@ -83,7 +83,6 @@ export function getProductAnalysisStatus( */ export function getProductFinding( productStatus: ProductStatus, - _analysisState: string, statusCounts: Record, submittedCount?: number, ): Finding | null {