This repository has no description
0

Configure Feed

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

semble / src / webapp / api-client / clients / mockGraphData.ts
7.3 kB 261 lines
1import type { GetGraphDataResponse, GraphNode, GraphEdge } from '@semble/types'; 2 3/** 4 * Configuration for mock graph data generation 5 */ 6interface MockGraphConfig { 7 nodeCount: number; 8 edgeDensity: number; // 0-1, percentage of possible edges to create 9 typeDistribution?: { 10 USER?: number; 11 URL?: number; 12 COLLECTION?: number; 13 NOTE?: number; 14 }; 15} 16 17/** 18 * Generate random mock graph data for performance testing 19 * 20 * @param config Configuration for data generation 21 * @returns Mock graph data with nodes and edges 22 * 23 * @example 24 * // Generate 1000 nodes with moderate edge density 25 * const mockData = generateMockGraphData({ nodeCount: 1000, edgeDensity: 0.01 }); 26 * 27 * // Generate 5000 nodes with custom type distribution 28 * const mockData = generateMockGraphData({ 29 * nodeCount: 5000, 30 * edgeDensity: 0.005, 31 * typeDistribution: { USER: 0.2, URL: 0.5, COLLECTION: 0.2, NOTE: 0.1 } 32 * }); 33 */ 34export function generateMockGraphData( 35 config: MockGraphConfig, 36): GetGraphDataResponse { 37 const { 38 nodeCount, 39 edgeDensity, 40 typeDistribution = { USER: 0.25, URL: 0.4, COLLECTION: 0.2, NOTE: 0.15 }, 41 } = config; 42 43 // Generate nodes 44 const nodes: GraphNode[] = []; 45 const nodeTypes: Array<GraphNode['type']> = [ 46 'USER', 47 'URL', 48 'COLLECTION', 49 'NOTE', 50 ]; 51 52 for (let i = 0; i < nodeCount; i++) { 53 // Determine node type based on distribution 54 const rand = Math.random(); 55 let cumulativeProb = 0; 56 let type: GraphNode['type'] = 'URL'; 57 58 for (const [nodeType, prob] of Object.entries(typeDistribution)) { 59 cumulativeProb += prob; 60 if (rand <= cumulativeProb) { 61 type = nodeType as GraphNode['type']; 62 break; 63 } 64 } 65 66 nodes.push(generateNode(i, type)); 67 } 68 69 // Generate edges 70 const edges: GraphEdge[] = []; 71 const edgeTypes: Array<GraphEdge['type']> = [ 72 'USER_FOLLOWS_USER', 73 'USER_FOLLOWS_COLLECTION', 74 'USER_AUTHORED_URL', 75 'NOTE_REFERENCES_URL', 76 'COLLECTION_CONTAINS_URL', 77 'URL_CONNECTS_URL', 78 ]; 79 80 // Calculate how many edges to create 81 const maxPossibleEdges = nodeCount * (nodeCount - 1); 82 const targetEdgeCount = Math.floor(maxPossibleEdges * edgeDensity); 83 84 let edgeId = 0; 85 for (let i = 0; i < targetEdgeCount; i++) { 86 // Pick two random nodes 87 const sourceIdx = Math.floor(Math.random() * nodeCount); 88 let targetIdx = Math.floor(Math.random() * nodeCount); 89 90 // Ensure source and target are different 91 while (targetIdx === sourceIdx) { 92 targetIdx = Math.floor(Math.random() * nodeCount); 93 } 94 95 const sourceNode = nodes[sourceIdx]; 96 const targetNode = nodes[targetIdx]; 97 98 // Determine appropriate edge type based on node types 99 const edgeType = getAppropriateEdgeType(sourceNode.type, targetNode.type); 100 if (!edgeType) continue; // Skip if no valid edge type for this combination 101 102 edges.push({ 103 id: `edge-${edgeId++}`, 104 source: sourceNode.id, 105 target: targetNode.id, 106 type: edgeType, 107 metadata: {}, 108 }); 109 } 110 111 console.log( 112 `Generated mock graph data: ${nodes.length} nodes, ${edges.length} edges`, 113 ); 114 115 // Return with pagination metadata (mock assumes single page with all data) 116 return { 117 nodes, 118 edges, 119 pagination: { 120 currentPage: 1, 121 totalPages: 1, 122 totalCount: nodes.length, 123 hasMore: false, 124 limit: nodes.length, 125 }, 126 }; 127} 128 129/** 130 * Generate a single node with realistic mock data 131 */ 132function generateNode(index: number, type: GraphNode['type']): GraphNode { 133 const id = `${type.toLowerCase()}-${index}`; 134 135 switch (type) { 136 case 'USER': 137 return { 138 id, 139 type, 140 label: `User ${index}`, 141 metadata: { 142 handle: `user${index}.bsky.social`, 143 name: `User ${index}`, 144 avatarUrl: `https://picsum.photos/seed/${index}/200/200`, 145 description: `This is a mock user profile for testing. User ${index}.`, 146 followerCount: Math.floor(Math.random() * 1000), 147 followingCount: Math.floor(Math.random() * 500), 148 }, 149 }; 150 151 case 'URL': 152 const urlTypes = ['article', 'video', 'book', 'research', 'link']; 153 const urlType = urlTypes[Math.floor(Math.random() * urlTypes.length)]; 154 return { 155 id, 156 type, 157 label: `${urlType.charAt(0).toUpperCase() + urlType.slice(1)} ${index}`, 158 metadata: { 159 url: `https://example.com/${urlType}/${index}`, 160 title: `Interesting ${urlType} about topic ${index}`, 161 description: `This is a mock ${urlType} for performance testing. Content ${index}.`, 162 author: `Author ${Math.floor(Math.random() * 100)}`, 163 siteName: 'Example.com', 164 libraryCount: Math.floor(Math.random() * 50), 165 urlType, 166 }, 167 }; 168 169 case 'COLLECTION': 170 const accessTypes = ['OPEN', 'CLOSED']; 171 const accessType = 172 accessTypes[Math.floor(Math.random() * accessTypes.length)]; 173 return { 174 id, 175 type, 176 label: `Collection ${index}`, 177 metadata: { 178 name: `Collection ${index}`, 179 description: `A curated collection of resources about topic ${index}`, 180 handle: `user${Math.floor(Math.random() * 50)}.bsky.social`, 181 rkey: `collection${index}`, 182 authorName: `User ${Math.floor(Math.random() * 50)}`, 183 cardCount: Math.floor(Math.random() * 100), 184 followerCount: Math.floor(Math.random() * 200), 185 accessType, 186 }, 187 }; 188 189 case 'NOTE': 190 return { 191 id, 192 type, 193 label: `Note ${index}`, 194 metadata: { 195 text: `This is a note with thoughts and insights about resource ${index}. Mock content for testing.`, 196 authorName: `User ${Math.floor(Math.random() * 50)}`, 197 parentUrl: `https://example.com/article/${Math.floor(Math.random() * 1000)}`, 198 }, 199 }; 200 201 default: 202 return { 203 id, 204 type: 'URL', 205 label: `Node ${index}`, 206 metadata: {}, 207 }; 208 } 209} 210 211/** 212 * Determine appropriate edge type based on source and target node types 213 */ 214function getAppropriateEdgeType( 215 sourceType: GraphNode['type'], 216 targetType: GraphNode['type'], 217): GraphEdge['type'] | null { 218 // Define valid edge type combinations 219 const validCombinations: Record<string, GraphEdge['type']> = { 220 'USER-USER': 'USER_FOLLOWS_USER', 221 'USER-COLLECTION': 'USER_FOLLOWS_COLLECTION', 222 'USER-URL': 'USER_AUTHORED_URL', 223 'NOTE-URL': 'NOTE_REFERENCES_URL', 224 'COLLECTION-URL': 'COLLECTION_CONTAINS_URL', 225 'URL-URL': 'URL_CONNECTS_URL', 226 }; 227 228 const key = `${sourceType}-${targetType}`; 229 return validCombinations[key] || null; 230} 231 232/** 233 * Preset configurations for common testing scenarios 234 */ 235export const MOCK_GRAPH_PRESETS = { 236 // Small graph for quick testing 237 small: { 238 nodeCount: 100, 239 edgeDensity: 0.02, 240 }, 241 // Medium graph for moderate testing 242 medium: { 243 nodeCount: 500, 244 edgeDensity: 0.01, 245 }, 246 // Large graph for performance testing 247 large: { 248 nodeCount: 2000, 249 edgeDensity: 0.005, 250 }, 251 // Extra large graph for stress testing 252 extraLarge: { 253 nodeCount: 5000, 254 edgeDensity: 0.002, 255 }, 256 // Dense small graph for complex visualization 257 denseSmall: { 258 nodeCount: 200, 259 edgeDensity: 0.05, 260 }, 261} as const;