Skip to content

[CHAIN] feat(ui): add graph interactions and filtered view#10756

Merged
pfe-nazaries merged 23 commits intoPROWLER-1273/react-flow-migrationfrom
PROWLER-1375/graph-interactions-filtered-view
May 5, 2026
Merged

[CHAIN] feat(ui): add graph interactions and filtered view#10756
pfe-nazaries merged 23 commits intoPROWLER-1273/react-flow-migrationfrom
PROWLER-1375/graph-interactions-filtered-view

Conversation

@pfe-nazaries
Copy link
Copy Markdown
Contributor

@pfe-nazaries pfe-nazaries commented Apr 16, 2026

🔗 Part of Chained PRs

Field Value
Feature Branch PROWLER-1273/react-flow-migration
Main PR #10686
Sub-task PROWLER-1375
Chain Position 3 of 5
Depends on #10705 (PR1 — React Flow core rendering)

Chain Overview

         ┌──────────────────────────┐
         │ PR0: Normalize data      │ ← #10701 🟢
         └────────────┬─────────────┘
                      │
         ┌────────────▼─────────────┐
         │ PR1: React Flow core     │ ← #10705 🟡
         │   rendering              │
         └────────────┬─────────────┘
                      │
         ┌────────────▼─────────────┐
         │ 📍 PR2: Interactions     │ ← THIS PR (#10756)
         │   🎯 MVP                 │
         └────────────┬─────────────┘
                      │
         ┌────────────▼─────────────┐
         │ PR3: Export + Minimap    │ ← #10800 🟡
         │   ✅ Phase 1 complete    │
         └────────────┬─────────────┘
                      │
         ┌────────────▼─────────────┐
         │ PR4: Test coverage       │ ← #10970 🟡
         │   🛡️ Hardening complete  │
         └────────────┬─────────────┘
                      │
         ┌────────────▼─────────────┐
         │ #10686 Main PR           │
         │   → master               │
         └──────────────────────────┘

Context

Part of chained PRs for the React Flow migration. This is the MVP milestone — it adds all user interactions to the graph: node selection, filtered view, and path highlighting.


Description

Implements graph interactions on top of the React Flow rendering from PR #10705.

Changes:

  • Tier 1 click: Clicking a resource node toggles visibility of connected finding nodes and their edges
  • Tier 2 click: Clicking a finding node (or any node in filtered view) enters a filtered subgraph view centered on that node, with re-computed Dagre layout
  • Path highlight on hover: Hovering a node highlights upstream/downstream edges with glow effect; clears on mouse leave
  • Node selection: Orange glow indicator on selected node, synced with detail panel
  • State resilience: Graph state resets properly on scan change and new query execution
  • DRY refactor: Extract shared NodeDetailPanel component for fullscreen and main detail panels
  • Type cleanup: Replace duplicated GraphCanvasProps with Omit<AttackPathGraphProps, "className">

Steps to review

  1. Navigate to Attack Paths, select a scan, execute a query
  2. Click a resource node → verify finding hexagons toggle on/off
  3. Click a finding node → verify filtered subgraph view with "Back to Full View" button
  4. Hover nodes → verify path edges highlight and clear cleanly
  5. Switch scans while in filtered view → verify state resets completely
  6. Open fullscreen dialog → verify detail panel shows NodeDetailContent

Checklist

Community Checklist
  • This feature/issue is listed in here or roadmap.prowler.com
  • Is it assigned to me, if not, request it via the issue/feature in here or Prowler Community Slack

UI

  • All issue/task requirements work as expected on the UI
  • Screenshots/Video of the functionality flow (if applicable) - Mobile (X < 640px)
  • Screenshots/Video of the functionality flow (if applicable) - Table (640px > X < 1024px)
  • Screenshots/Video of the functionality flow (if applicable) - Desktop (X > 1024px)
  • Ensure new entries are added to CHANGELOG.md, if applicable.

License

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@pfe-nazaries pfe-nazaries requested a review from a team as a code owner April 16, 2026 14:30
@pfe-nazaries pfe-nazaries requested a review from a team April 16, 2026 14:30
Pablo F.G and others added 9 commits April 17, 2026 14:41
- Narrow GraphEdge.source/target from string | object to string
- Remove typeof guards in adapter, graph component, and utilities
- Remove unused panX, panY, zoomLevel fields from graph state store
- Delete orphaned NodeRelationships component (never integrated)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Aligns with design decision D4 — GraphCanvas now owns layoutWithDagre()
and node enrichment (selection, hasFindings), making AttackPathGraph a
thin wrapper around ReactFlowProvider. This prepares PR2 where
GraphCanvas needs setEdges() ownership for hover highlight.

Also removes unused AttackPathGraphRef deprecated alias and isFilteredView
prop from outer component.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rewrite attack-path-graph.tsx from D3 imperative SVG to React Flow declarative components
- Add pure layoutWithDagre() function using @dagrejs/dagre maintained fork
- Create custom node components: FindingNode (hexagon), ResourceNode (pill), InternetNode (globe)
- Implement outer/inner component split (ReactFlowProvider constraint)
- Disable export button temporarily (re-enabled in PR3 with html-to-image)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace require() with ESM import for @dagrejs/dagre
- Pre-compute resourcesWithFindings set to avoid O(n*e) per-node loop
- Extract isFindingNode helper to deduplicate label checks
- Remove no-op handleWheel handler and unused initialNodeId prop
- Replace dangerouslySetInnerHTML with style children
- Add aria-hidden to SVG defs and role/aria-label to graph container

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace nodes.find() inside edges loop with precomputed Map
- Fix misleading complexity comment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Tier 1 click: toggle finding visibility on resource nodes
- Add Tier 2 click: enter filtered subgraph view on finding nodes
- Add path highlight on hover with upstream/downstream edges
- Add node selection with visual indicator and detail panel sync
- Fix graph state not resetting on scan change
- Extract NodeDetailPanel to deduplicate fullscreen and main panels

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace nodes.find() inside edges loop with pre-built Map
- Update graph-interactions spec to reflect D4 render-body derivation
- Update Ctrl+scroll spec to document zoomOnPinch native handling

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Post-rebase conflict resolution: extend the local NodeDetailPanel with
onViewFinding / viewFindingLoading props so the master-added finding
drawer flow remains wired through the extracted panel component.
@pfe-nazaries pfe-nazaries force-pushed the PROWLER-1375/graph-interactions-filtered-view branch from 87f804b to d01b532 Compare April 17, 2026 13:06
…n' into PROWLER-1375/graph-interactions-filtered-view
Pablo F.G and others added 5 commits April 22, 2026 16:42
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Aligns with design decision D4 — GraphCanvas now owns layoutWithDagre()
and node enrichment (selection, hasFindings), making AttackPathGraph a
thin wrapper around ReactFlowProvider. This prepares PR2 where
GraphCanvas needs setEdges() ownership for hover highlight.

Also removes unused AttackPathGraphRef deprecated alias and isFilteredView
prop from outer component.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rewrite attack-path-graph.tsx from D3 imperative SVG to React Flow declarative components
- Add pure layoutWithDagre() function using @dagrejs/dagre maintained fork
- Create custom node components: FindingNode (hexagon), ResourceNode (pill), InternetNode (globe)
- Implement outer/inner component split (ReactFlowProvider constraint)
- Disable export button temporarily (re-enabled in PR3 with html-to-image)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace require() with ESM import for @dagrejs/dagre
- Pre-compute resourcesWithFindings set to avoid O(n*e) per-node loop
- Extract isFindingNode helper to deduplicate label checks
- Remove no-op handleWheel handler and unused initialNodeId prop
- Replace dangerouslySetInnerHTML with style children
- Add aria-hidden to SVG defs and role/aria-label to graph container

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace nodes.find() inside edges loop with precomputed Map
- Fix misleading complexity comment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@Alan-TheGentleman Alan-TheGentleman left a comment

Choose a reason for hiding this comment

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

The local interaction state does not reset when the graph data changes. expandedResources and hoveredNodeId live inside GraphCanvas, but nothing clears them after a new query or scan switch, so this can leak stale expansion/highlight state into the next graph and it also makes the "state resets properly" claim false. Please reset both when data changes, or remount the graph with a stable key derived from the active scan/query.

@Alan-TheGentleman
Copy link
Copy Markdown
Contributor

Alan-TheGentleman commented Apr 27, 2026

Question: was it intentional to stop passing onViewFinding / viewFindingLoading through the extracted NodeDetailPanel in fullscreen mode? As written, this looks like it regresses the in-app finding drawer flow and can also leave the related finding buttons visible and enabled in the compact fullscreen panel even though clicking them becomes a no-op.

@Alan-TheGentleman
Copy link
Copy Markdown
Contributor

Following up on the reuse question from the previous PR and the comment here: #10705 (comment). Do you think it is worth extracting the repeated node primitives for all three node components here instead of keeping every detail duplicated inside each one? I would still keep FindingNode, ResourceNode, and InternetNode as separate components because their shapes and labels differ, but hidden handles, label truncation, and the visual-state logic look shared enough to reduce repetition without forcing a generic node renderer.

Pablo F.G added 7 commits April 29, 2026 11:55
Address Alan's review comment on #10705: deduplicate the pieces that all
three React Flow node components share without forcing a generic node
renderer. FindingNode, ResourceNode and InternetNode stay separate; only
the boilerplate is extracted.

- HiddenHandles: invisible target/source handles used identically by all
  three nodes.
- truncateLabel: shared label truncation helper used by FindingNode and
  ResourceNode (24/22 chars).
- resolveNodeColors: layered fill/border resolution covering selection
  highlight and finding-alert state. Per-node strokeWidth is intentionally
  kept local since each node has its own visual weight.
Address Alan's CHANGES_REQUESTED on #10705: add automated coverage so
the new React Flow rendering contract is guarded against regressions.

- _lib/layout.test.ts: deterministic checks on layoutWithDagre — empty
  input, node typing/dimensions by labels, run-to-run determinism,
  top-left position offset, container relationship inversion (RUNS_IN
  & friends with originalSource/originalTarget preserved on the rfEdge),
  finding-edge animation/className, and stable rfEdge IDs.
- graph-controls.test.tsx: GraphControls Export button is disabled and
  surfaces "Export available soon" without an onExport callback, and
  enabled + invokes the callback when one is provided.

Mounting the full AttackPathGraph with React Flow in jsdom is fragile
(ResizeObserver, dimensions, edge measurement); that coverage is left
for the next chained PR which introduces Vitest Browser Mode.
Test additions are an internal contract; user-facing changelogs only use
the standard Added/Changed/Fixed/Removed/Security sections.
- Skip browser-replay artifacts under .expect/ to prevent the
  prettier plugin from crashing the lint pipeline.
- Clear expanded resources and hovered node when graph data changes,
  preventing stale interaction state across scans and query runs.
- Forward View Finding handler to the fullscreen detail panel so
  related-finding actions are no longer no-ops.
…375/graph-interactions-filtered-view

Bring chained PR #10705 up to date in #10756 so both target the same
feature branch with consistent React Flow refactor.

Conflict resolutions:
- nodes/{finding,internet,resource}-node.tsx: take 1374 versions (use
  HiddenHandles primitive + resolveNodeColors / truncateLabel helpers)
- _lib/graph-utils.ts: keep 1375 perf optimization (nodeLabelMap O(1))
- _components/graph/attack-path-graph.tsx: keep 1375 (already includes
  React Flow refactor + interactions, filtered view, hover highlight)
- attack-paths-page.tsx: keep 1375 (already extracts NodeDetailPanel)

Inbound from 1374: shared HiddenHandles, resolveNodeColors,
truncateLabel, layoutWithDagre tests, graph-controls tests.
Reverts the workaround in b3e08a6. The .expect/ directory was
local tooling output and is no longer kept under the worktree, so
the ignore rule in eslint.config.mjs is dead weight.
@pfe-nazaries
Copy link
Copy Markdown
Contributor Author

@Alan-TheGentleman comment addressed and branch updated with previous chained PR

Copy link
Copy Markdown
Contributor

@Alan-TheGentleman Alan-TheGentleman left a comment

Choose a reason for hiding this comment

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

f878f121 resets expandedResources and hoveredNodeId on data change and wires onViewFinding / viewFindingLoading through NodeDetailPanel in fullscreen. Approving.

Copy link
Copy Markdown
Contributor

@Alan-TheGentleman Alan-TheGentleman left a comment

Choose a reason for hiding this comment

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

f878f121 resets expandedResources and hoveredNodeId on data change and wires onViewFinding / viewFindingLoading through NodeDetailPanel in fullscreen. Approving.

@pfe-nazaries pfe-nazaries merged commit 1d54244 into PROWLER-1273/react-flow-migration May 5, 2026
1 check passed
@pfe-nazaries pfe-nazaries deleted the PROWLER-1375/graph-interactions-filtered-view branch May 5, 2026 07:00
Alan-TheGentleman pushed a commit that referenced this pull request May 8, 2026
Co-authored-by: Pablo F.G <pablo.fernandez@prowler.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants