This repository has no description
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}