Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ function ArchWorkspaceInner({ model, source }: ArchWorkspaceInnerProps) {
const reactFlowInstance = useReactFlow();
const containerRef = useRef<HTMLDivElement>(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,
Expand Down Expand Up @@ -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();

Expand All @@ -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;
Expand Down Expand Up @@ -378,6 +380,9 @@ function ArchWorkspaceInner({ model, source }: ArchWorkspaceInnerProps) {
onExportSvg={handleExportSvg}
onToggleFullscreen={handleToggleFullscreen}
isFullscreen={isFullscreen}
relationshipTypes={relationshipTypes}
relationshipType={relationshipType}
onRelationshipTypeChange={setRelationshipType}
/>
{edgesLimited && (
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ interface GraphToolbarProps {
onExportSvg: () => void;
onToggleFullscreen: () => void;
isFullscreen: boolean;
relationshipTypes: string[];
relationshipType: string;
onRelationshipTypeChange: (value: string) => void;
}

export function GraphToolbar({
Expand All @@ -35,6 +38,7 @@ export function GraphToolbar({
onExportSvg,
onToggleFullscreen,
isFullscreen,
relationshipTypes, relationshipType, onRelationshipTypeChange,
}: GraphToolbarProps) {
const {
searchQuery, setSearchQuery, showGrid, setShowGrid,
Expand Down Expand Up @@ -62,6 +66,10 @@ export function GraphToolbar({
</div>

<div className="pointer-events-auto ml-auto flex max-w-full items-center gap-1 overflow-x-auto rounded-md">
<select aria-label="Canvas relationship type" value={relationshipType} onChange={event => onRelationshipTypeChange(event.target.value)} className="rounded-md border border-border bg-card px-2 py-1.5 text-xs">
<option value="all">All relationship types</option>
{relationshipTypes.map(type => <option key={type} value={type}>{type}</option>)}
</select>
<ToolbarButton icon={ZoomIn} onClick={onZoomIn} title="Zoom In" />
<ToolbarButton icon={ZoomOut} onClick={onZoomOut} title="Zoom Out" />
<ToolbarButton icon={Maximize2} onClick={onFitView} title="Fit View" />
Expand Down
14 changes: 14 additions & 0 deletions apps/frontend/src/features/architecture/layout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
8 changes: 5 additions & 3 deletions apps/frontend/src/features/architecture/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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),
Expand Down
Loading