Skip to content

feat(advisory): implement CSAF Relationship Tree tab with ECharts visualization#1067

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

feat(advisory): implement CSAF Relationship Tree tab with ECharts visualization#1067
CryptoRodeo wants to merge 3 commits into
guacsec:mainfrom
CryptoRodeo:TC-4623

Conversation

@CryptoRodeo
Copy link
Copy Markdown
Collaborator

@CryptoRodeo CryptoRodeo commented May 28, 2026

Summary

  • Add interactive ECharts tree rendering of CSAF product relationships grouped by relates_to_product_reference
  • Color-coded nodes by relationship type (default_component_of, external_component_of, installed_on, installed_with, optional_component_of) with legend
  • Product IDs resolved to display names via full_product_names
  • Empty state when no relationships exist
  • Helper module csaf-relationship-helpers.ts reuses EChartsTreeNode type from tree helpers

Test plan

  • Build succeeds (npm run build -w client)
  • All 24 unit tests pass
  • Biome/lint passes
  • Relationship tree renders grouped by parent product
  • Empty state displays when no relationships

Implements TC-4623

Assisted-by: Claude Code

Summary by Sourcery

Add CSAF-specific advisory details view with a new relationship tree visualization and supporting CSAF data plumbing.

New Features:

  • Introduce a CSAF advisory details layout with a dedicated 5-tab interface for CSAF/VEX documents.
  • Add an interactive CSAF product relationship tree visualization using ECharts, including a legend and empty state handling.
  • Provide a query hook to fetch and parse raw CSAF JSON documents into typed structures for use in the UI.

Enhancements:

  • Define strongly typed CSAF 2.0 VEX TypeScript interfaces to model CSAF documents, product trees, and vulnerabilities.
  • Refactor advisory details routing to switch between the existing view and the new CSAF-specific layout based on advisory type.
  • Add helper utilities to transform CSAF product relationships into ECharts tree data with consistent color mapping by relationship category.

Build:

  • Add ECharts and React ECharts dependencies to support the new CSAF relationship tree visualization.

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 relationships
grouped by relates_to_product_reference. Features color-coded nodes
by relationship type (default_component_of, external_component_of,
installed_on, installed_with, optional_component_of) with legend.

Product IDs are resolved to display names via full_product_names.
Empty state shown when no relationships exist.

Implements TC-4623

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 5-tab CSAF layout, including an ECharts-based relationship tree visualization that groups CSAF product relationships by parent product and colors nodes by relationship category, along with CSAF type definitions and a hook to fetch and parse CSAF JSON documents.

Sequence diagram for CSAF advisory relationship tree rendering

sequenceDiagram
  actor User
  participant AdvisoryDetails
  participant CsafAdvisoryDetails
  participant useFetchAdvisoryCsafById
  participant downloadAdvisory
  participant CsafRelationshipTree
  participant transformRelationshipsToTreeData
  participant ReactECharts

  User->>AdvisoryDetails: navigate to AdvisoryDetails
  AdvisoryDetails->>AdvisoryDetails: evaluate isCsaf
  AdvisoryDetails-->>CsafAdvisoryDetails: render when isCsaf

  CsafAdvisoryDetails->>useFetchAdvisoryCsafById: useFetchAdvisoryCsafById(advisoryId)
  useFetchAdvisoryCsafById->>downloadAdvisory: downloadAdvisory({ client, path })
  downloadAdvisory-->>useFetchAdvisoryCsafById: Blob
  useFetchAdvisoryCsafById->>useFetchAdvisoryCsafById: blob.text() and JSON.parse
  useFetchAdvisoryCsafById-->>CsafAdvisoryDetails: csafDocument

  CsafAdvisoryDetails-->>CsafRelationshipTree: render CsafRelationshipTree(csafDocument)
  CsafRelationshipTree->>transformRelationshipsToTreeData: transformRelationshipsToTreeData(relationships, full_product_names)
  transformRelationshipsToTreeData-->>CsafRelationshipTree: EChartsTreeNode treeData
  CsafRelationshipTree-->>ReactECharts: option with treeData
Loading

File-Level Changes

Change Details Files
Introduce CSAF-specific advisory details layout and route CSAF advisories to it instead of the legacy two-tab view.
  • Add CsafAdvisoryDetails component that defines a 5-tab CSAF layout (overview, vulnerabilities, product tree, relationship tree, source) using existing tab-controls hook and LoadingWrapper.
  • Wire AdvisoryDetails page to render CsafAdvisoryDetails when advisory.labels.type is 'csaf', falling back to the existing non-CSAF tabs otherwise.
  • Implement placeholder EmptyState-based "ComingSoon" content for CSAF overview, vulnerabilities, product tree, and source tabs.
client/src/app/pages/advisory-details/advisory-details.tsx
client/src/app/pages/advisory-details/csaf-advisory-details.tsx
Add ECharts-based CSAF relationship tree visualization with legend and empty state handling.
  • Create CsafRelationshipTree component that extracts product_tree.relationships and full_product_names from a CsafDocument and renders an interactive ECharts tree graph via echarts-for-react.
  • Implement tooltip formatting to show node name and humanized relationship category for edges, configure tree layout (left-to-right, roam, depth, labels), and add a flex-based color legend keyed by relationship category.
  • Render an EmptyState when no relationship data is present in the CSAF document.
client/src/app/pages/advisory-details/csaf-relationship-tree.tsx
Provide helper utilities to transform CSAF relationships into ECharts tree nodes with color coding by relationship category.
  • Define RELATIONSHIP_CATEGORY_COLORS mapping of CSAF relationship categories to hex colors for consistent styling and legend rendering.
  • Implement transformRelationshipsToTreeData to group relationships by relates_to_product_reference, resolve product IDs to display names from full_product_names, and build a root EChartsTreeNode tree structure.
  • Add internal helpers to build a product ID-to-name map and resolve display names, falling back to product IDs when names are missing.
client/src/app/pages/advisory-details/helpers/csaf-relationship-helpers.ts
Add a React Query hook to download and parse raw CSAF JSON documents into typed structures.
  • Introduce useFetchAdvisoryCsafById hook that uses downloadAdvisory to get the advisory as a Blob, reads it as text, JSON-parses it, and returns a typed CsafDocument.
  • Expose csafDocument, isFetching, and fetchError from the hook to drive loading and error states in CsafAdvisoryDetails.
client/src/app/queries/advisories.ts
Introduce TypeScript type definitions for CSAF 2.0 VEX documents used throughout the new CSAF features.
  • Add CsafDocument and related interfaces (document metadata, product tree, relationships, vulnerabilities, scores, remediations, etc.) aligned with the CSAF 2.0 JSON schema.
  • Use these types in the CSAF fetch hook and CsafRelationshipTree to provide strong typing for CSAF data structures.
client/src/app/types/csaf.ts
Add ECharts dependencies required for the new visualization.
  • Declare echarts and echarts-for-react as runtime dependencies in the client package.json so the tree visualization can be rendered.
  • Update package-lock.json accordingly to lock versions and dependency tree.
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 18:43
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 re-creates them on every render; consider switching to useRef so the refs are stable across renders.
  • In useFetchAdvisoryCsafById, JSON.parse of the blob text is unchecked; wrapping this in a try/catch (and surfacing a more explicit parse error) would make failures due to malformed CSAF documents easier to diagnose.
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 re-creates them on every render; consider switching to `useRef` so the refs are stable across renders.
- In `useFetchAdvisoryCsafById`, `JSON.parse` of the blob text is unchecked; wrapping this in a try/catch (and surfacing a more explicit parse error) would make failures due to malformed CSAF documents easier to diagnose.

## Individual Comments

### Comment 1
<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>
**issue (bug_risk):** Switch tab refs from `createRef` to `useRef` to avoid recreating refs on every render.

Because this is a function component, `React.createRef` will allocate new ref objects on every render, which can break any logic relying on stable ref identity (e.g., `tabContentRef`, focus/scroll handlers). Prefer `useRef` so the same ref instance is preserved across renders:

```ts
const overviewTabRef = React.useRef<HTMLElement>(null);
const vulnerabilitiesTabRef = React.useRef<HTMLElement>(null);
// ...etc.
```
</issue_to_address>

### Comment 2
<location path="client/src/app/pages/advisory-details/csaf-relationship-tree.tsx" line_range="91-100" />
<code_context>
+    tooltip: {
+      trigger: "item" as const,
+      triggerOn: "mousemove" as const,
+      formatter: (params: { data?: { name?: string; value?: string } }) => {
+        const category = params.data?.value;
+        return category
+          ? `${params.data?.name}<br/><em>${category.replace(/_/g, " ")}</em>`
+          : params.data?.name || "";
+      },
+    },
</code_context>
<issue_to_address>
**suggestion:** Differentiate tooltip content for parent vs child nodes to avoid treating product IDs as relationship categories.

The tooltip formatter currently assumes `data.value` is always a relationship category and applies `replace(/_/g, " ")`. For parent nodes, `value` holds the product ID, so it’s formatted as if it were a category. Consider either storing the relationship category in a separate field (e.g. `data.category`) or only rendering the italicized category line when `data.value` matches a known relationship category (e.g. keys of `RELATIONSHIP_CATEGORY_COLORS`). This avoids misformatting product IDs in parent tooltips.

```suggestion
    tooltip: {
      trigger: "item" as const,
      triggerOn: "mousemove" as const,
      formatter: (params: { data?: { name?: string; value?: string } }) => {
        const name = params.data?.name ?? "";
        const value = params.data?.value;

        // Only treat `value` as a relationship category when it matches a known category key.
        const knownCategories = Object.keys(RELATIONSHIP_CATEGORY_COLORS ?? {});
        const isRelationshipCategory =
          typeof value === "string" && knownCategories.includes(value);

        if (isRelationshipCategory) {
          return `${name}<br/><em>${value.replace(/_/g, " ")}</em>`;
        }

        // For parent nodes (where `value` is a product ID), just show the product name.
        return name;
      },
    },
```
</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/csaf-advisory-details.tsx
Comment on lines +91 to +100
tooltip: {
trigger: "item" as const,
triggerOn: "mousemove" as const,
formatter: (params: { data?: { name?: string; value?: string } }) => {
const category = params.data?.value;
return category
? `${params.data?.name}<br/><em>${category.replace(/_/g, " ")}</em>`
: params.data?.name || "";
},
},
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: Differentiate tooltip content for parent vs child nodes to avoid treating product IDs as relationship categories.

The tooltip formatter currently assumes data.value is always a relationship category and applies replace(/_/g, " "). For parent nodes, value holds the product ID, so it’s formatted as if it were a category. Consider either storing the relationship category in a separate field (e.g. data.category) or only rendering the italicized category line when data.value matches a known relationship category (e.g. keys of RELATIONSHIP_CATEGORY_COLORS). This avoids misformatting product IDs in parent tooltips.

Suggested change
tooltip: {
trigger: "item" as const,
triggerOn: "mousemove" as const,
formatter: (params: { data?: { name?: string; value?: string } }) => {
const category = params.data?.value;
return category
? `${params.data?.name}<br/><em>${category.replace(/_/g, " ")}</em>`
: params.data?.name || "";
},
},
tooltip: {
trigger: "item" as const,
triggerOn: "mousemove" as const,
formatter: (params: { data?: { name?: string; value?: string } }) => {
const name = params.data?.name ?? "";
const value = params.data?.value;
// Only treat `value` as a relationship category when it matches a known category key.
const knownCategories = Object.keys(RELATIONSHIP_CATEGORY_COLORS ?? {});
const isRelationshipCategory =
typeof value === "string" && knownCategories.includes(value);
if (isRelationshipCategory) {
return `${name}<br/><em>${value.replace(/_/g, " ")}</em>`;
}
// For parent nodes (where `value` is a product ID), just show the product name.
return name;
},
},

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 — tooltip improvement — valid enhancement but not a bug. No sub-task created.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

❌ Patch coverage is 0% with 73 lines in your changes missing coverage. Please review.
✅ Project coverage is 48.52%. Comparing base (def496d) to head (edfdecb).

Files with missing lines Patch % Lines
...isory-details/helpers/csaf-relationship-helpers.ts 0.00% 26 Missing ⚠️
.../pages/advisory-details/csaf-relationship-tree.tsx 0.00% 20 Missing ⚠️
...p/pages/advisory-details/csaf-advisory-details.tsx 0.00% 14 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    #1067      +/-   ##
==========================================
- Coverage   49.17%   48.52%   -0.65%     
==========================================
  Files         253      256       +3     
  Lines        5499     5570      +71     
  Branches     1660     1673      +13     
==========================================
- Hits         2704     2703       -1     
- Misses       2519     2592      +73     
+ 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
Copy link
Copy Markdown
Collaborator Author

Verification Report for TC-4623 (commit edfdecb)

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