Skip to content

feat(advisory): CSAF VEX Visualizer — all tabs combined [TC-4612]#1070

Draft
CryptoRodeo wants to merge 3 commits into
guacsec:mainfrom
CryptoRodeo:TC-4612-full-changes
Draft

feat(advisory): CSAF VEX Visualizer — all tabs combined [TC-4612]#1070
CryptoRodeo wants to merge 3 commits into
guacsec:mainfrom
CryptoRodeo:TC-4612-full-changes

Conversation

@CryptoRodeo
Copy link
Copy Markdown
Collaborator

@CryptoRodeo CryptoRodeo commented May 28, 2026

Summary

Combined PR with all changes for the CSAF VEX Visualizer feature (TC-4612). This merges all 7 implementation task branches for testing the complete feature as a single unit.

Changes included:

  • TC-4618 — CSAF 2.0 TypeScript types, useFetchAdvisoryCsafById query hook, ECharts dependency
  • TC-4619 — CSAF advisory type detection (labels.type === "csaf") and conditional 5-tab layout
  • TC-4620 — Overview tab: metadata card, impact summary bar, remediation donut chart, references, revision history, notes
  • TC-4621 — Vulnerabilities tab: per-CVE cards sorted by severity, CVSS breakdown, affected products (+N more), remediations by category, references
  • TC-4622 — Product Tree tab: ECharts interactive tree with color-coded categories and auto-collapse
  • TC-4623 — Relationship Tree tab: ECharts tree grouped by relates_to_product_reference with color-coded types
  • TC-4624 — Source tab: raw JSON CodeBlock with copy-to-clipboard and original advisory link

New files (15):

  • client/src/app/types/csaf.ts
  • client/src/app/pages/advisory-details/csaf-advisory-details.tsx
  • client/src/app/pages/advisory-details/csaf-overview.tsx
  • client/src/app/pages/advisory-details/csaf-vulnerabilities.tsx
  • client/src/app/pages/advisory-details/csaf-product-tree.tsx
  • client/src/app/pages/advisory-details/csaf-relationship-tree.tsx
  • client/src/app/pages/advisory-details/csaf-source.tsx
  • client/src/app/pages/advisory-details/components/csaf-vulnerability-card.tsx
  • client/src/app/pages/advisory-details/components/csaf-cvss-details.tsx
  • client/src/app/pages/advisory-details/components/csaf-remediations.tsx
  • client/src/app/pages/advisory-details/helpers/csaf-tree-helpers.ts
  • client/src/app/pages/advisory-details/helpers/csaf-relationship-helpers.ts

Modified files:

  • client/src/app/queries/advisories.ts — added useFetchAdvisoryCsafById hook
  • client/src/app/pages/advisory-details/advisory-details.tsx — CSAF type detection + conditional rendering
  • client/package.json — added echarts, echarts-for-react

Test plan

  • Build succeeds
  • All 24 unit tests pass
  • Non-CSAF advisories retain default 2-tab layout
  • CSAF advisories show 5-tab layout with all tabs functional

Implements TC-4612

Assisted-by: Claude Code

Summary by Sourcery

Add a CSAF-specific advisory details experience with a dedicated 5-tab VEX visualizer and typed CSAF document support.

New Features:

  • Introduce a CSAF-aware advisory details layout that switches from the default tabs when labels.type is "csaf".
  • Add a CSAF VEX 5-tab interface (Overview, Vulnerabilities, Product Tree, Relationship Tree, Source) for visualizing advisory contents.
  • Provide rich CSAF Overview and Vulnerabilities presentations including metadata, severity and remediation summaries, and per-CVE cards.
  • Expose interactive ECharts-based visualizations for CSAF product trees and product relationships.
  • Add a Source tab to view the raw CSAF JSON and link to the original advisory document.
  • Introduce strongly-typed TypeScript models for CSAF 2.0 VEX documents and a React Query hook for fetching them.

Enhancements:

  • Extend advisory fetching logic with a query hook that downloads, parses, and returns typed CSAF documents for CSAF advisories.

Build:

  • Add ECharts and the React ECharts wrapper as client-side dependencies to support interactive CSAF visualizations.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented May 28, 2026

Reviewer's Guide

Implements a CSAF-specific advisory details experience: defines CSAF 2.0 TypeScript types, adds a CSAF JSON fetch hook, wires CSAF type detection into advisory details to switch to a new 5-tab VEX visualizer, and introduces several CSAF-focused UI components including overview metrics, per-CVE details, product and relationship trees using ECharts, and a raw JSON source view.

Sequence diagram for CSAF document fetch and visualization

sequenceDiagram
  actor User
  participant AdvisoryDetails
  participant CsafAdvisoryDetails
  participant useFetchAdvisoryCsafById
  participant downloadAdvisory
  participant CsafTabs

  User->>AdvisoryDetails: open advisory details
  AdvisoryDetails->>AdvisoryDetails: compute isCsaf
  AdvisoryDetails->>CsafAdvisoryDetails: render(advisoryId) [isCsaf]
  CsafAdvisoryDetails->>useFetchAdvisoryCsafById: call(advisoryId)
  useFetchAdvisoryCsafById->>downloadAdvisory: downloadAdvisory(client, path)
  downloadAdvisory-->>useFetchAdvisoryCsafById: Blob response
  useFetchAdvisoryCsafById->>useFetchAdvisoryCsafById: blob.text() + JSON.parse
  useFetchAdvisoryCsafById-->>CsafAdvisoryDetails: { csafDocument, isFetching, fetchError }
  CsafAdvisoryDetails->>CsafTabs: render with csafDocument
  CsafTabs-->>User: CSAF overview, vulnerabilities, trees, source
Loading

Flow diagram for CSAF vs default advisory details layout

flowchart LR
  AdvisoryDetails["AdvisoryDetails component"] --> FetchAdvisory["useFetchAdvisoryById(advisoryId)"]
  FetchAdvisory --> AdvisoryLoaded["advisory loaded"]
  AdvisoryLoaded --> CheckType{"advisory.labels.type === csaf"}
  CheckType -->|true| CsafDetails["CsafAdvisoryDetails(advisoryId)"]
  CheckType -->|false| DefaultTabs["Overview + Vulnerabilities tabs"]

  CsafDetails --> CsafTabs["5 CSAF tabs (overview, vulnerabilities, product_tree, relationship_tree, source)"]
  DefaultTabs --> NonCsafTabs["2 default tabs (info, vulnerabilities)"]
Loading

File-Level Changes

Change Details Files
Introduce typed CSAF 2.0 domain model and query hook for fetching CSAF JSON for an advisory.
  • Add comprehensive TypeScript interfaces for CSAF documents, product trees, and vulnerabilities.
  • Add a react-query hook that downloads the advisory as a Blob, parses JSON, and exposes a typed CSAF document plus loading/error state.
  • Wire the new hook into CSAF-specific UI components.
client/src/app/types/csaf.ts
client/src/app/queries/advisories.ts
Extend advisory details to detect CSAF advisories and render a dedicated 5-tab CSAF VEX visualizer instead of the existing 2-tab layout.
  • Detect CSAF advisories via advisory.labels.type === "csaf".
  • Preserve the existing Info/Vulnerabilities tabbed layout for non-CSAF advisories.
  • Render a new CsafAdvisoryDetails component for CSAF advisories, with its own tab persistence key and 5 tabs (overview, vulnerabilities, product tree, relationship tree, source).
client/src/app/pages/advisory-details/advisory-details.tsx
client/src/app/pages/advisory-details/csaf-advisory-details.tsx
Implement CSAF Overview and Vulnerabilities tabs including derived metrics and rich per-CVE cards.
  • Add an overview tab that shows document metadata, severity distribution bar chart, remediation-status donut chart, references, revision history, and notes.
  • Derive severity and remediation category counts from the CSAF vulnerabilities array.
  • Add vulnerabilities tab that sorts CSAF vulnerabilities by CVSS severity and renders per-CVE cards with metadata, CVSS details, affected products, remediations, and references.
  • Provide reusable subcomponents for CVSS metric breakdown and remediation grouping/linkification.
client/src/app/pages/advisory-details/csaf-overview.tsx
client/src/app/pages/advisory-details/csaf-vulnerabilities.tsx
client/src/app/pages/advisory-details/components/csaf-vulnerability-card.tsx
client/src/app/pages/advisory-details/components/csaf-cvss-details.tsx
client/src/app/pages/advisory-details/components/csaf-remediations.tsx
Add interactive ECharts-based visualizations for CSAF product and relationship trees with helper transformations and legends.
  • Introduce helpers to convert CSAF branch data into ECharts tree node structures, including category color mapping and auto-collapse of very large subtrees.
  • Introduce helpers to convert CSAF relationship data into an ECharts tree grouped by relates_to_product_reference, with color-coded relationship categories.
  • Implement Product Tree and Relationship Tree tabs that render the transformed data via echarts-for-react, with tooltips, pan/zoom, initial depth, and PatternFly EmptyStates when no data is present.
  • Add small legends explaining color coding for branch/relationship categories.
client/src/app/pages/advisory-details/csaf-product-tree.tsx
client/src/app/pages/advisory-details/csaf-relationship-tree.tsx
client/src/app/pages/advisory-details/helpers/csaf-tree-helpers.ts
client/src/app/pages/advisory-details/helpers/csaf-relationship-helpers.ts
Expose the raw CSAF JSON source and original advisory link, and add required charting dependencies.
  • Add a Source tab that shows a link to the "self" reference (original advisory URL) and a pretty-printed JSON CodeBlock of the CSAF document.
  • Ensure copy-friendly JSON via CodeBlockCode and keep layout simple within the tab.
  • Add echarts and echarts-for-react dependencies to the client package manifest and lockfile.
client/src/app/pages/advisory-details/csaf-source.tsx
client/package.json
package-lock.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@CryptoRodeo CryptoRodeo marked this pull request as draft May 28, 2026 19:02
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • In CsafAdvisoryDetails, the tab content refs are created with React.createRef() inside the component body, which recreates them on each render; consider switching to useRef so the refs are stable across renders and behave correctly with tabContentRef.
  • The LinkifiedText component in csaf-remediations.tsx uses a global regex (/g) and then calls urlRegex.test(part) inside map, which will give incorrect results because test advances lastIndex; either remove the global flag or use a fresh regex/test per part.
  • In useFetchAdvisoryCsafById, JSON parse failures will be surfaced as non-Axios errors but are typed as AxiosError; consider wrapping JSON.parse in a try/catch and either throwing an AxiosError-compatible shape or widening the fetchError type to reflect non-network parsing errors.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `CsafAdvisoryDetails`, the tab content refs are created with `React.createRef()` inside the component body, which recreates them on each render; consider switching to `useRef` so the refs are stable across renders and behave correctly with `tabContentRef`.
- The `LinkifiedText` component in `csaf-remediations.tsx` uses a global regex (`/g`) and then calls `urlRegex.test(part)` inside `map`, which will give incorrect results because `test` advances `lastIndex`; either remove the global flag or use a fresh regex/test per part.
- In `useFetchAdvisoryCsafById`, JSON parse failures will be surfaced as non-Axios errors but are typed as `AxiosError`; consider wrapping `JSON.parse` in a try/catch and either throwing an `AxiosError`-compatible shape or widening the `fetchError` type to reflect non-network parsing errors.

## Individual Comments

### Comment 1
<location path="client/src/app/pages/advisory-details/advisory-details.tsx" line_range="92" />
<code_context>
     useDeleteAdvisoryMutation(onDeleteAdvisorySuccess, onDeleteAdvisoryError);

-  // Tabs
+  const isCsaf = advisory?.labels.type === "csaf";
+
+  // Tabs (default non-CSAF layout)
</code_context>
<issue_to_address>
**issue:** Guard against `labels` being undefined when deriving `isCsaf`.

`advisory?.labels.type` still throws if `advisory` exists but `labels` is null/undefined. Use `const isCsaf = advisory?.labels?.type === "csaf";` so both `advisory` and `labels` are safely optional.
</issue_to_address>

### Comment 2
<location path="client/src/app/pages/advisory-details/csaf-overview.tsx" line_range="178-187" />
<code_context>
+          )}
+          {cvss && (
+            <FlexItem>
+              <SeverityShieldAndText
+                value={
+                  cvss.baseSeverity.toLowerCase() as
+                    | "critical"
+                    | "important"
+                    | "moderate"
+                    | "low"
+                    | "none"
+                }
+                score={cvss.baseScore}
+              />
+            </FlexItem>
+          )}
</code_context>
<issue_to_address>
**issue (bug_risk):** Align CSAF severities with the `SeverityShieldAndText` expected values.

Here and in the impact summary, CSAF/CVSS severities (`CRITICAL`, `HIGH`, `MEDIUM`, etc.) are just lowercased and passed to `SeverityShieldAndText`, which expects only `"critical" | "important" | "moderate" | "low" | "none"`. That means `"high"` and `"medium"` won’t map correctly. Please normalize the CSAF severities first (e.g. `HIGH -> important`, `MEDIUM -> moderate`, and any unknown value -> `none`) before passing them into `SeverityShieldAndText`.
</issue_to_address>

### Comment 3
<location path="client/src/app/pages/advisory-details/components/csaf-vulnerability-card.tsx" line_range="171-155" />
<code_context>
+            )}
+
+          {/* References */}
+          {vulnerability.references && vulnerability.references.length > 0 && (
+            <FlexItem>
+              <ExpandableSection toggleText="References" isExpanded={false}>
+                <List isPlain>
+                  {vulnerability.references.map((ref, i) => (
+                    <ListItem key={`ref-${i}`}>
+                      <a
+                        href={ref.url}
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        {ref.summary || ref.url} <ExternalLinkAltIcon />
+                      </a>
+                    </ListItem>
+                  ))}
+                </List>
+              </ExpandableSection>
+            </FlexItem>
+          )}
</code_context>
<issue_to_address>
**issue (bug_risk):** The "References" expandable section is permanently collapsed.

This `ExpandableSection` is hard-coded with `isExpanded={false}` and no `onToggle`, so the references are never visible. Either let it be uncontrolled by omitting `isExpanded`, or control `isExpanded` via component state so the user can expand it.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

useDeleteAdvisoryMutation(onDeleteAdvisorySuccess, onDeleteAdvisoryError);

// Tabs
const isCsaf = advisory?.labels.type === "csaf";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Guard against labels being undefined when deriving isCsaf.

advisory?.labels.type still throws if advisory exists but labels is null/undefined. Use const isCsaf = advisory?.labels?.type === "csaf"; so both advisory and labels are safely optional.

Comment on lines +178 to +187
<SeverityShieldAndText
value={
doc.aggregate_severity.text.toLowerCase() as
| "critical"
| "important"
| "moderate"
| "low"
| "none"
}
score={null}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Align CSAF severities with the SeverityShieldAndText expected values.

Here and in the impact summary, CSAF/CVSS severities (CRITICAL, HIGH, MEDIUM, etc.) are just lowercased and passed to SeverityShieldAndText, which expects only "critical" | "important" | "moderate" | "low" | "none". That means "high" and "medium" won’t map correctly. Please normalize the CSAF severities first (e.g. HIGH -> important, MEDIUM -> moderate, and any unknown value -> none) before passing them into SeverityShieldAndText.

+{hiddenCount} more
</Button>
)}
</ExpandableSection>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The "References" expandable section is permanently collapsed.

This ExpandableSection is hard-coded with isExpanded={false} and no onToggle, so the references are never visible. Either let it be uncontrolled by omitting isExpanded, or control isExpanded via component state so the user can expand it.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

❌ Patch coverage is 0% with 324 lines in your changes missing coverage. Please review.
✅ Project coverage is 46.43%. Comparing base (def496d) to head (f28cd67).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
...t/src/app/pages/advisory-details/csaf-overview.tsx 0.00% 94 Missing ⚠️
...ory-details/components/csaf-vulnerability-card.tsx 0.00% 43 Missing ⚠️
.../advisory-details/components/csaf-remediations.tsx 0.00% 37 Missing ⚠️
...isory-details/helpers/csaf-relationship-helpers.ts 0.00% 26 Missing ⚠️
...pp/pages/advisory-details/csaf-vulnerabilities.tsx 0.00% 24 Missing ⚠️
.../pages/advisory-details/csaf-relationship-tree.tsx 0.00% 23 Missing ⚠️
...c/app/pages/advisory-details/csaf-product-tree.tsx 0.00% 19 Missing ⚠️
...p/pages/advisory-details/csaf-advisory-details.tsx 0.00% 17 Missing ⚠️
...ages/advisory-details/helpers/csaf-tree-helpers.ts 0.00% 15 Missing ⚠️
...ent/src/app/pages/advisory-details/csaf-source.tsx 0.00% 8 Missing ⚠️
... and 3 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1070      +/-   ##
==========================================
- Coverage   49.17%   46.43%   -2.74%     
==========================================
  Files         253      264      +11     
  Lines        5499     5819     +320     
  Branches     1660     1790     +130     
==========================================
- Hits         2704     2702       -2     
- Misses       2519     2842     +323     
+ Partials      276      275       -1     
Flag Coverage Δ
unit 1.90% <0.00%> (-0.12%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@CryptoRodeo CryptoRodeo force-pushed the TC-4612-full-changes branch from bcd547b to ce8a3c6 Compare May 28, 2026 19:36
Signed-off-by: Bryan Ramos <bramos@redhat.com>
@CryptoRodeo CryptoRodeo force-pushed the TC-4612-full-changes branch from ce8a3c6 to d30eccd Compare May 28, 2026 20:14
manual fix.

Signed-off-by: Bryan Ramos <bramos@redhat.com>
- Replace custom severity bars with PatternFly ChartBar showing affected
  products per severity
- Restructure overview card with document title as header and
  severity/TLP badges
- Add two-column grid layout to vulnerability cards
- Link CVE tracking IDs to vulnerability details
- Use human-readable date format throughout
- Move tree legends above charts with usage hints, expand relationship
  legend with edge styles
- Rename Product Tree tab to Products

Signed-off-by: Bryan Ramos <bramos@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant