Skip to content

feat(advisory): implement CSAF Product Tree tab with ECharts tree visualization#1066

Draft
CryptoRodeo wants to merge 3 commits into
guacsec:mainfrom
CryptoRodeo:TC-4622
Draft

feat(advisory): implement CSAF Product Tree tab with ECharts tree visualization#1066
CryptoRodeo wants to merge 3 commits into
guacsec:mainfrom
CryptoRodeo:TC-4622

Conversation

@CryptoRodeo
Copy link
Copy Markdown
Collaborator

@CryptoRodeo CryptoRodeo commented May 28, 2026

Summary

  • Add interactive ECharts tree rendering of CSAF product_tree.branches hierarchy (vendor → product family → product name → product version)
  • Left-to-right tree layout with click to expand/collapse, scroll to zoom, drag to pan
  • Color-coded nodes by branch category (vendor, product_name, product_version, etc.) with legend
  • Auto-collapse subtrees with >40 leaves for performance
  • Empty state when no product tree data exists
  • React.useMemo for data transformation performance
  • Helper module csaf-tree-helpers.ts for reuse by Relationship Tree tab

Test plan

  • Build succeeds (npm run build -w client)
  • All 24 unit tests pass
  • Biome/lint passes
  • Tree renders with correct hierarchy from branch data
  • Empty state displays when no product tree

Implements TC-4622

Assisted-by: Claude Code

Summary by Sourcery

Add CSAF-specific advisory details view with a product tree visualization and supporting CSAF data types and query.

New Features:

  • Introduce a CSAF-specific advisory details layout with dedicated tabs for overview, vulnerabilities, product tree, relationship tree, and source.
  • Render CSAF product_tree branches as an interactive ECharts tree with zoom, pan, expand/collapse behavior, and a category color legend.
  • Add a React Query hook to download and parse the raw CSAF JSON document for use in CSAF visualizations.

Enhancements:

  • Add strongly typed CSAF 2.0 VEX TypeScript interfaces for documents, product trees, and vulnerabilities.
  • Wire advisory details page to switch between the existing view and the CSAF-specific tabbed view based on advisory type.

Build:

  • Add ECharts and echarts-for-react as new client-side dependencies.

Define TypeScript interfaces for the full CSAF 2.0 VEX document
structure (CsafDocument, CsafVulnerability, CsafProductTree, etc.)
and add useFetchAdvisoryCsafById query hook that fetches raw CSAF JSON
via the downloadAdvisory endpoint and parses the Blob into typed data.

Also add echarts and echarts-for-react dependencies for upcoming
tree visualization components.

Implements TC-4618

Assisted-by: Claude Code
When advisory labels.type === "csaf", render a CSAF-specific 5-tab
layout (Overview, Vulnerabilities, Product Tree, Relationship Tree,
Source) instead of the default 2-tab Info/Vulnerabilities view.

The CsafAdvisoryDetails component fetches the parsed CSAF document
via useFetchAdvisoryCsafById and provides placeholder content for
each tab that subsequent tasks will replace with real implementations.

Implements TC-4619

Assisted-by: Claude Code
…ualization

Add interactive ECharts tree rendering of CSAF product_tree.branches
hierarchy (vendor → product family → product name → product version).

Features:
- Left-to-right tree layout with expand/collapse, zoom, and pan
- Color-coded nodes by branch category with legend
- Auto-collapse subtrees with >40 leaves
- Empty state when no product tree data exists
- React.useMemo for data transformation performance

Implements TC-4622

Assisted-by: Claude Code
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented May 28, 2026

Reviewer's Guide

Implements a CSAF-specific advisory details view with a new Product Tree tab that renders CSAF product_tree.branches as an interactive ECharts tree, adds CSAF typings and query hook to fetch the raw CSAF JSON, and conditionally switches advisory details layout for CSAF advisories while introducing reusable helpers for tree transformation and styling.

Sequence diagram for CSAF advisory product tree rendering

sequenceDiagram
  actor User
  participant AdvisoryDetails
  participant CsafAdvisoryDetails
  participant useFetchAdvisoryCsafById
  participant downloadAdvisory
  participant CsafProductTree
  participant transformBranchesToTreeData
  participant ReactECharts

  User->>AdvisoryDetails: open advisory (csaf type)
  AdvisoryDetails->>CsafAdvisoryDetails: render with advisoryId
  CsafAdvisoryDetails->>useFetchAdvisoryCsafById: advisoryId
  useFetchAdvisoryCsafById->>downloadAdvisory: { client, path.key }
  downloadAdvisory-->>useFetchAdvisoryCsafById: Blob
  useFetchAdvisoryCsafById-->>CsafAdvisoryDetails: csafDocument
  CsafAdvisoryDetails->>CsafProductTree: csafDocument
  CsafProductTree->>transformBranchesToTreeData: product_tree.branches
  transformBranchesToTreeData-->>CsafProductTree: EChartsTreeNode root
  CsafProductTree->>ReactECharts: option.series[0].data = [root]
  ReactECharts-->>User: interactive tree visualization
Loading

File-Level Changes

Change Details Files
Add CSAF-specific advisory details layout with dedicated tabs and conditional routing from the generic advisory details page.
  • Introduce CsafAdvisoryDetails component with a 5-tab PatternFly Tabs layout (overview, vulnerabilities, product tree, relationship tree, source).
  • Wire CsafAdvisoryDetails into AdvisoryDetails, switching to this CSAF layout when advisory.labels.type is 'csaf' while preserving the existing non-CSAF tabs otherwise.
  • Use useTabControls with a new persistenceKeyPrefix for CSAF tabs and create individual tab refs and TabContent sections, currently using a ComingSoon placeholder for unimplemented tabs.
client/src/app/pages/advisory-details/advisory-details.tsx
client/src/app/pages/advisory-details/csaf-advisory-details.tsx
Introduce a CSAF Product Tree tab that visualizes product_tree.branches using ECharts tree with category-based colors, auto-collapsing, and an empty state.
  • Create CsafProductTree component that reads csafDocument.product_tree.branches, memoizes transformation to ECharts tree data, and renders via echarts-for-react with a left-to-right tree configuration and tooltips.
  • Implement a CategoryLegend subcomponent showing color swatches and labels for each branch category using PatternFly Flex layout.
  • Render an EmptyState when no product tree data or branches are available, with appropriate messaging and iconography.
client/src/app/pages/advisory-details/csaf-product-tree.tsx
Add reusable helper utilities for transforming CSAF branches into ECharts tree nodes with category colors and auto-collapse behavior.
  • Define EChartsTreeNode interface and BRANCH_CATEGORY_COLORS map for CSAF branch categories, providing default color for unknown categories.
  • Implement countLeaves to compute leaf counts in a branch subtree and transformBranch to create ECharts nodes with collapsed set when leaf count exceeds AUTO_COLLAPSE_THRESHOLD.
  • Expose transformBranchesToTreeData to build a virtual root node ('Product Tree') whose children are transformed top-level branches for consumption by CsafProductTree and future relationship tree visualizations.
client/src/app/pages/advisory-details/helpers/csaf-tree-helpers.ts
Add CSAF domain model typings and a React Query hook to fetch and parse raw CSAF JSON documents for use in the new CSAF views.
  • Create a comprehensive CsafDocument TypeScript model reflecting the CSAF 2.0 VEX schema, including document metadata, product tree, and vulnerability structures.
  • Implement useFetchAdvisoryCsafById hook using downloadAdvisory to retrieve the advisory as a Blob, convert to text, parse JSON, and return typed CsafDocument along with loading and error state.
  • Use the new hook inside CsafAdvisoryDetails to drive the CSAF tabs and LoadingWrapper.
client/src/app/types/csaf.ts
client/src/app/queries/advisories.ts
Introduce ECharts dependencies necessary for the CSAF tree visualization.
  • Add echarts and echarts-for-react packages to the client workspace dependencies.
  • Regenerate/modify package-lock.json to capture the new dependencies and their transitive requirements.
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

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 2 issues, and left some high level feedback:

  • In CsafAdvisoryDetails, the tab refs are created with React.createRef() inside the component body, which recreates them on every render; switch these to useRef so the refs remain stable across renders and avoid potential focus/TabContent issues.
  • The countLeaves helper in csaf-tree-helpers.ts is called once per branch in transformBranch, making the overall transform O(n²) on large trees; consider refactoring to compute leaf counts in a single post-order traversal and reuse the results when building EChartsTreeNodes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `CsafAdvisoryDetails`, the tab refs are created with `React.createRef()` inside the component body, which recreates them on every render; switch these to `useRef` so the refs remain stable across renders and avoid potential focus/TabContent issues.
- The `countLeaves` helper in `csaf-tree-helpers.ts` is called once per branch in `transformBranch`, making the overall transform O(n²) on large trees; consider refactoring to compute leaf counts in a single post-order traversal and reuse the results when building `EChartsTreeNode`s.

## 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 (bug_risk):** Guard against missing `labels` when checking CSAF type.

If `labels` can be undefined, `advisory?.labels.type` will still throw. Use `advisory?.labels?.type === "csaf"` (or a safe accessor) to avoid a runtime error when `labels` is missing.
</issue_to_address>

### Comment 2
<location path="client/src/app/pages/advisory-details/csaf-advisory-details.tsx" line_range="58-62" />
<code_context>
+    ],
+  });
+
+  const overviewTabRef = React.createRef<HTMLElement>();
+  const vulnerabilitiesTabRef = React.createRef<HTMLElement>();
+  const productTreeTabRef = React.createRef<HTMLElement>();
+  const relationshipTreeTabRef = React.createRef<HTMLElement>();
+  const sourceTabRef = React.createRef<HTMLElement>();
+
+  return (
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Use `useRef` instead of `createRef` for tab content refs inside a function component.

Using `React.createRef` in a function component recreates these refs on every render, which can break the linkage between each `Tab`’s `tabContentRef` and its `TabContent`. Using `React.useRef<HTMLElement>(null)` instead keeps each ref stable across renders, matching how the PatternFly tabs expect refs to work.

```suggestion
  const overviewTabRef = React.useRef<HTMLElement | null>(null);
  const vulnerabilitiesTabRef = React.useRef<HTMLElement | null>(null);
  const productTreeTabRef = React.useRef<HTMLElement | null>(null);
  const relationshipTreeTabRef = React.useRef<HTMLElement | null>(null);
  const sourceTabRef = React.useRef<HTMLElement | null>(null);
```
</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.

Comment thread client/src/app/pages/advisory-details/advisory-details.tsx
Comment on lines +58 to +62
const overviewTabRef = React.createRef<HTMLElement>();
const vulnerabilitiesTabRef = React.createRef<HTMLElement>();
const productTreeTabRef = React.createRef<HTMLElement>();
const relationshipTreeTabRef = React.createRef<HTMLElement>();
const sourceTabRef = React.createRef<HTMLElement>();
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.

suggestion (bug_risk): Use useRef instead of createRef for tab content refs inside a function component.

Using React.createRef in a function component recreates these refs on every render, which can break the linkage between each Tab’s tabContentRef and its TabContent. Using React.useRef<HTMLElement>(null) instead keeps each ref stable across renders, matching how the PatternFly tabs expect refs to work.

Suggested change
const overviewTabRef = React.createRef<HTMLElement>();
const vulnerabilitiesTabRef = React.createRef<HTMLElement>();
const productTreeTabRef = React.createRef<HTMLElement>();
const relationshipTreeTabRef = React.createRef<HTMLElement>();
const sourceTabRef = React.createRef<HTMLElement>();
const overviewTabRef = React.useRef<HTMLElement | null>(null);
const vulnerabilitiesTabRef = React.useRef<HTMLElement | null>(null);
const productTreeTabRef = React.useRef<HTMLElement | null>(null);
const relationshipTreeTabRef = React.useRef<HTMLElement | null>(null);
const sourceTabRef = React.useRef<HTMLElement | null>(null);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[sdlc-workflow/verify-pr] Classified as suggestion — createRef vs useRef is a pre-existing codebase convention. No sub-task created.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

❌ Patch coverage is 0% with 60 lines in your changes missing coverage. Please review.
✅ Project coverage is 48.64%. Comparing base (def496d) to head (22cdbfc).

Files with missing lines Patch % Lines
...c/app/pages/advisory-details/csaf-product-tree.tsx 0.00% 19 Missing ⚠️
...ages/advisory-details/helpers/csaf-tree-helpers.ts 0.00% 15 Missing ⚠️
...p/pages/advisory-details/csaf-advisory-details.tsx 0.00% 13 Missing ⚠️
client/src/app/queries/advisories.ts 0.00% 8 Missing ⚠️
...rc/app/pages/advisory-details/advisory-details.tsx 0.00% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1066      +/-   ##
==========================================
- Coverage   49.17%   48.64%   -0.54%     
==========================================
  Files         253      256       +3     
  Lines        5499     5557      +58     
  Branches     1660     1670      +10     
==========================================
- Hits         2704     2703       -1     
- Misses       2519     2579      +60     
+ Partials      276      275       -1     
Flag Coverage Δ
unit 1.99% <0.00%> (-0.03%) ⬇️

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 marked this pull request as draft May 28, 2026 18:39
@CryptoRodeo
Copy link
Copy Markdown
Collaborator Author

Verification Report for TC-4622 (commit 22cdbfc)

Check Result Details
Review Feedback PASS 2 suggestions (all non-blocking)
Root-Cause Investigation N/A No sub-tasks created
Scope Containment PASS All files in scope per task description
Diff Size PASS Appropriate for task scope
Commit Traceability PASS Conventional Commits format
Sensitive Patterns PASS No secrets detected
CI Status PASS Build succeeds
Acceptance Criteria PASS 8/8 criteria met
Test Quality N/A No test files in scope
Test Change Classification N/A No test files modified
Verification Commands PASS Build succeeds, 24/24 tests pass

Overall: PASS


This comment was AI-generated by sdlc-workflow/verify-pr v0.9.1.

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