This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

semble / src / webapp / features / graph / lib / queries / useUrlGraphData.tsx
2.8 kB 88 lines
1import { useQuery } from '@tanstack/react-query'; 2import { useMemo } from 'react'; 3import { graphKeys } from '../graphKeys'; 4import { apiClient } from '@/api-client/ApiClient'; 5import type { 6 ProcessedGraphData, 7 ExtendedGraphNode, 8 ExtendedGraphEdge, 9} from '../../types'; 10import { calculateNodeSize, getNodeColor } from '../utils/nodeStyles'; 11 12/** 13 * Hook to fetch and process URL-centric sub-graph data with depth-based traversal 14 * Fetches all nodes and edges within N hops of the target URL 15 * Calculates connection counts, node sizes, and colors for all nodes 16 */ 17export default function useUrlGraphData(url: string, depth: number = 1) { 18 // Fetch URL sub-graph data 19 const query = useQuery({ 20 queryKey: graphKeys.url(url, depth), 21 queryFn: () => apiClient.getUrlGraphData({ url, depth }), 22 staleTime: 5 * 60 * 1000, // 5 minutes 23 enabled: !!url, // Only fetch if URL is provided 24 }); 25 26 // Process the graph data 27 const processedData = useMemo((): ProcessedGraphData => { 28 if (!query.data) { 29 return { nodes: [], links: [] }; 30 } 31 32 const { nodes, edges } = query.data; 33 34 // Deduplicate nodes by ID (defensive - server should already dedupe) 35 const nodeMap = new Map<string, ExtendedGraphNode>(); 36 37 // Calculate connection counts for all nodes 38 const connectionCounts: Record<string, number> = {}; 39 edges.forEach((edge) => { 40 connectionCounts[edge.source] = (connectionCounts[edge.source] || 0) + 1; 41 connectionCounts[edge.target] = (connectionCounts[edge.target] || 0) + 1; 42 }); 43 44 // Process nodes: deduplicate, add connection count, size, and color 45 nodes.forEach((node) => { 46 if (!nodeMap.has(node.id)) { 47 const connectionCount = connectionCounts[node.id] || 0; 48 49 nodeMap.set(node.id, { 50 ...node, 51 connectionCount, 52 val: calculateNodeSize(connectionCount), 53 color: getNodeColor(node.type), 54 // Track when node was added for smooth fade-in animation 55 __addedAt: Date.now(), 56 }); 57 } 58 }); 59 60 const processedNodes = Array.from(nodeMap.values()); 61 62 // Create a set of valid node IDs for edge validation 63 const validNodeIds = new Set(processedNodes.map((n) => n.id)); 64 65 // Process edges: filter out invalid edges and add visual properties 66 const processedEdges: ExtendedGraphEdge[] = edges 67 .filter((edge) => { 68 return validNodeIds.has(edge.source) && validNodeIds.has(edge.target); 69 }) 70 .map((edge) => ({ 71 ...edge, 72 value: 1, // Default thickness 73 })); 74 75 return { 76 nodes: processedNodes, 77 links: processedEdges, 78 }; 79 }, [query.data]); 80 81 return { 82 data: processedData, 83 isLoading: query.isLoading, 84 isError: query.isError, 85 error: query.error, 86 isSuccess: query.isSuccess, 87 }; 88}