From 4375357b20d78ffc6dd9908ce2a32c1b54d37430 Mon Sep 17 00:00:00 2001 From: Shaurya K Sharma Date: Fri, 9 Oct 2026 00:18:51 +0100 Subject: [PATCH] fix(frontend): add explicit canvas relationship filtering (#507) --- .../architecture/components/ArchWorkspace.tsx | 11 ++++++++--- .../architecture/components/GraphToolbar.tsx | 8 ++++++++ .../src/features/architecture/layout.test.ts | 14 ++++++++++++++ apps/frontend/src/features/architecture/layout.ts | 8 +++++--- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/apps/frontend/src/features/architecture/components/ArchWorkspace.tsx b/apps/frontend/src/features/architecture/components/ArchWorkspace.tsx index 7a45179d..9d3b630d 100644 --- a/apps/frontend/src/features/architecture/components/ArchWorkspace.tsx +++ b/apps/frontend/src/features/architecture/components/ArchWorkspace.tsx @@ -54,6 +54,8 @@ function ArchWorkspaceInner({ model, source }: ArchWorkspaceInnerProps) { const reactFlowInstance = useReactFlow(); const containerRef = useRef(null); const [isFullscreen, setIsFullscreen] = useState(false); + const [relationshipType, setRelationshipType] = useState('all'); + const relationshipTypes = useMemo(() => [...new Set(model.edges.map(edge => edge.type))].sort(), [model.edges]); const { selectedNodeId, @@ -101,7 +103,7 @@ function ArchWorkspaceInner({ model, source }: ArchWorkspaceInnerProps) { const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState( - selectVisibleEdges(layoutEdges, edgesLimited, selectedNodeId), + selectVisibleEdges(layoutEdges, edgesLimited, selectedNodeId, relationshipType), ); const nodesInitialized = useNodesInitialized(); @@ -112,8 +114,8 @@ function ArchWorkspaceInner({ model, source }: ArchWorkspaceInnerProps) { // In large-graph mode only the selected module's relationships are drawn; // selecting a module changes the edges but never the node layout or camera. useEffect(() => { - setEdges(selectVisibleEdges(layoutEdges, edgesLimited, selectedNodeId)); - }, [layoutEdges, edgesLimited, selectedNodeId, setEdges]); + setEdges(selectVisibleEdges(layoutEdges, edgesLimited, selectedNodeId, relationshipType)); + }, [layoutEdges, edgesLimited, selectedNodeId, relationshipType, setEdges]); useEffect(() => { if (!nodesInitialized) return; @@ -378,6 +380,9 @@ function ArchWorkspaceInner({ model, source }: ArchWorkspaceInnerProps) { onExportSvg={handleExportSvg} onToggleFullscreen={handleToggleFullscreen} isFullscreen={isFullscreen} + relationshipTypes={relationshipTypes} + relationshipType={relationshipType} + onRelationshipTypeChange={setRelationshipType} /> {edgesLimited && (
void; onToggleFullscreen: () => void; isFullscreen: boolean; + relationshipTypes: string[]; + relationshipType: string; + onRelationshipTypeChange: (value: string) => void; } export function GraphToolbar({ @@ -35,6 +38,7 @@ export function GraphToolbar({ onExportSvg, onToggleFullscreen, isFullscreen, + relationshipTypes, relationshipType, onRelationshipTypeChange, }: GraphToolbarProps) { const { searchQuery, setSearchQuery, showGrid, setShowGrid, @@ -62,6 +66,10 @@ export function GraphToolbar({
+ diff --git a/apps/frontend/src/features/architecture/layout.test.ts b/apps/frontend/src/features/architecture/layout.test.ts index 8297b56f..584d68fe 100644 --- a/apps/frontend/src/features/architecture/layout.test.ts +++ b/apps/frontend/src/features/architecture/layout.test.ts @@ -160,3 +160,17 @@ describe('large-graph edge limiting', () => { expect(selectVisibleEdges(all, false, 'web')).toBe(all); }); }); + + +it('filters canvas relationship types without changing the full evidence edge set', () => { + const all = [ + {id:'a',source:'one',target:'two',data:{relationshipType:'import'}}, + {id:'b',source:'one',target:'three',data:{relationshipType:'dependency'}}, + {id:'c',source:'four',target:'three',data:{relationshipType:'import'}}, + ]; + expect(selectVisibleEdges(all,false,null,'import').map(edge=>edge.id)).toEqual(['a','c']); + expect(selectVisibleEdges(all,true,'one','import').map(edge=>edge.id)).toEqual(['a']); + expect(selectVisibleEdges(all,true,null,'import')).toEqual([]); + expect(selectVisibleEdges(all,false,null)).toEqual(all); + expect(all).toHaveLength(3); +}); diff --git a/apps/frontend/src/features/architecture/layout.ts b/apps/frontend/src/features/architecture/layout.ts index bd497813..367a5a6c 100644 --- a/apps/frontend/src/features/architecture/layout.ts +++ b/apps/frontend/src/features/architecture/layout.ts @@ -25,10 +25,11 @@ const NODE_GAP = 35; export const LARGE_GRAPH_EDGE_THRESHOLD = 500; /** Edges to put on the canvas: all of them, or only the focused node's in large-graph mode. */ -export function selectVisibleEdges(edges: Edge[], limited: boolean, focusNodeId: string | null): Edge[] { - if (!limited) return edges; +export function selectVisibleEdges(edges: Edge[], limited: boolean, focusNodeId: string | null, relationshipType = 'all'): Edge[] { + const filtered = relationshipType === 'all' ? edges : edges.filter(edge => edge.data?.relationshipType === relationshipType); + if (!limited) return filtered; if (!focusNodeId) return []; - return edges.filter((edge) => edge.source === focusNodeId || edge.target === focusNodeId); + return filtered.filter((edge) => edge.source === focusNodeId || edge.target === focusNodeId); } export function getLayoutedElements( @@ -116,6 +117,7 @@ export function getLayoutedElements( source: edge.source, target: edge.target, type: 'smoothstep', + data: { relationshipType: edge.type }, animated: edge.type === 'data-flow' || edge.type === 'event', style: { stroke: getEdgeColor(edge.type),