Skip to content
Merged
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
88 changes: 44 additions & 44 deletions frontend/src/components/DependencyGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ export function DependencyGraph({ repoId, apiUrl, apiKey }: DependencyGraphProps

const data = await response.json()

// Convert to React Flow format
const flowNodes: Node[] = data.nodes.map((node: any) => {
const fileName = node.label || node.id.split('/').pop()
const fullPath = node.id
Expand Down Expand Up @@ -145,10 +144,10 @@ export function DependencyGraph({ repoId, apiUrl, apiKey }: DependencyGraphProps
source: edge.source,
target: edge.target,
animated: false,
style: { stroke: '#94a3b8', strokeWidth: 1.5 },
style: { stroke: '#4b5563', strokeWidth: 1.5 },
markerEnd: {
type: MarkerType.ArrowClosed,
color: '#94a3b8',
color: '#4b5563',
},
}))

Expand All @@ -172,7 +171,6 @@ export function DependencyGraph({ repoId, apiUrl, apiKey }: DependencyGraphProps
const handleNodeClick = useCallback((event: any, node: Node) => {
setHighlightedNode(node.id)

// Highlight connected nodes
const connectedNodeIds = new Set<string>()
connectedNodeIds.add(node.id)

Expand Down Expand Up @@ -232,67 +230,67 @@ export function DependencyGraph({ repoId, apiUrl, apiKey }: DependencyGraphProps

if (loading) {
return (
<div className="card p-12 text-center">
<div className="w-16 h-16 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin mx-auto mb-4" />
<p className="text-gray-600">Building dependency graph...</p>
<div className="p-12 text-center">
<div className="w-16 h-16 border-4 border-blue-500/20 border-t-blue-500 rounded-full animate-spin mx-auto mb-4" />
<p className="text-gray-400">Building dependency graph...</p>
</div>
)
}

return (
<div className="space-y-6">
{/* Metrics + Controls */}
<div className="p-6 space-y-6">
{/* Metrics */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="card p-5">
<div className="text-sm text-gray-600 mb-1">Total Files</div>
<div className="text-3xl font-bold text-gray-900">{allNodes.length}</div>
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5">
<div className="text-sm text-gray-400 mb-1">Total Files</div>
<div className="text-3xl font-bold text-white">{allNodes.length}</div>
</div>
<div className="card p-5">
<div className="text-sm text-gray-600 mb-1">Dependencies</div>
<div className="text-3xl font-bold text-blue-600">{edges.length}</div>
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5">
<div className="text-sm text-gray-400 mb-1">Dependencies</div>
<div className="text-3xl font-bold text-blue-400">{edges.length}</div>
</div>
<div className="card p-5">
<div className="text-sm text-gray-600 mb-1">Avg per File</div>
<div className="text-3xl font-bold text-gray-900">
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5">
<div className="text-sm text-gray-400 mb-1">Avg per File</div>
<div className="text-3xl font-bold text-white">
{metrics?.avg_dependencies?.toFixed(1) || 0}
</div>
</div>
<div className="card p-5">
<div className="text-sm text-gray-600 mb-1">Showing</div>
<div className="text-3xl font-bold text-green-600">{nodes.length}</div>
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5">
<div className="text-sm text-gray-400 mb-1">Showing</div>
<div className="text-3xl font-bold text-green-400">{nodes.length}</div>
</div>
</div>

{/* Filter Controls */}
<div className="card p-5">
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5">
<div className="flex flex-wrap items-center gap-4">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={filterCritical}
onChange={(e) => setFilterCritical(e.target.checked)}
className="w-4 h-4 text-blue-600 rounded focus:ring-2 focus:ring-blue-500"
className="w-4 h-4 bg-white/5 border-white/10 rounded focus:ring-2 focus:ring-blue-500"
/>
<span className="text-sm text-gray-700">Show only critical files (≥3 deps)</span>
<span className="text-sm text-gray-300">Show only critical files (≥3 deps)</span>
</label>

<div className="flex items-center gap-2">
<label className="text-sm text-gray-700">Min dependencies:</label>
<label className="text-sm text-gray-300">Min dependencies:</label>
<input
type="range"
min="0"
max="10"
value={minDeps}
onChange={(e) => setMinDeps(Number(e.target.value))}
className="w-32"
className="w-32 accent-blue-500"
/>
<span className="text-sm font-mono text-gray-900">{minDeps}</span>
<span className="text-sm font-mono text-white">{minDeps}</span>
</div>

{highlightedNode && (
<button
onClick={resetHighlight}
className="text-sm px-3 py-1.5 bg-red-50 text-red-600 rounded hover:bg-red-100 border border-red-200"
className="text-sm px-3 py-1.5 bg-red-500/10 text-red-400 rounded-lg hover:bg-red-500/20 border border-red-500/20 transition-colors"
>
Clear highlight
</button>
Expand All @@ -302,15 +300,15 @@ export function DependencyGraph({ repoId, apiUrl, apiKey }: DependencyGraphProps

{/* Most Critical Files */}
{metrics?.most_critical_files && metrics.most_critical_files.length > 0 && (
<div className="card p-5">
<h3 className="text-sm font-semibold mb-3 text-gray-900">Most Critical Files</h3>
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5">
<h3 className="text-sm font-semibold mb-3 text-white">Most Critical Files</h3>
<div className="space-y-2">
{metrics.most_critical_files.slice(0, 5).map((item: any, idx: number) => (
<div key={idx} className="flex items-center justify-between text-sm">
<span className="font-mono text-gray-700 truncate flex-1">
<span className="font-mono text-gray-300 truncate flex-1">
{item.file.split('/').slice(-2).join('/')}
</span>
<span className="badge-danger ml-2">
<span className="ml-2 px-2 py-0.5 text-xs bg-red-500/10 text-red-400 border border-red-500/20 rounded">
{item.dependents} dependents
</span>
</div>
Expand All @@ -320,7 +318,7 @@ export function DependencyGraph({ repoId, apiUrl, apiKey }: DependencyGraphProps
)}

{/* Graph Visualization */}
<div className="card p-0 overflow-hidden" style={{ height: '700px' }}>
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl overflow-hidden" style={{ height: '700px' }}>
<ReactFlow
nodes={nodes}
edges={edges}
Expand All @@ -333,39 +331,41 @@ export function DependencyGraph({ repoId, apiUrl, apiKey }: DependencyGraphProps
minZoom={0.1}
maxZoom={2}
>
<Background />
<Controls />
<Background color="#1f1f23" gap={16} />
<Controls className="!bg-[#111113] !border-white/10 !rounded-lg [&>button]:!bg-[#111113] [&>button]:!border-white/10 [&>button]:!text-gray-400 [&>button:hover]:!bg-white/10" />
<MiniMap
nodeColor={(node) => {
const style = node.style as any
return style?.background || '#6b7280'
}}
maskColor="rgba(0, 0, 0, 0.1)"
maskColor="rgba(0, 0, 0, 0.5)"
className="!bg-[#111113] !border-white/10"
/>
</ReactFlow>
</div>

<div className="card p-5">
<h3 className="text-sm font-semibold mb-3 text-gray-900">Graph Legend</h3>
{/* Legend */}
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5">
<h3 className="text-sm font-semibold mb-3 text-white">Graph Legend</h3>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 text-xs">
<div className="flex items-center gap-2">
<div className="w-4 h-4 rounded" style={{ background: '#3776ab' }} />
<span className="text-gray-600">Python</span>
<span className="text-gray-400">Python</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 rounded" style={{ background: '#3178c6' }} />
<span className="text-gray-600">TypeScript</span>
<span className="text-gray-400">TypeScript</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 rounded" style={{ background: '#f7df1e' }} />
<span className="text-gray-600">JavaScript</span>
<span className="text-gray-400">JavaScript</span>
</div>
<div className="flex items-center gap-2">
<div className="w-1 h-4 bg-blue-600" />
<span className="text-gray-600">Dependency</span>
<div className="w-1 h-4 bg-blue-500" />
<span className="text-gray-400">Dependency</span>
</div>
</div>
<div className="mt-3 pt-3 border-t border-gray-200 text-xs text-gray-600">
<div className="mt-3 pt-3 border-t border-white/5 text-xs text-gray-500">
💡 Click any node to highlight its dependencies • Drag to pan • Scroll to zoom
</div>
</div>
Expand Down
76 changes: 38 additions & 38 deletions frontend/src/components/ImpactAnalyzer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,47 +60,47 @@ export function ImpactAnalyzer({ repoId, apiUrl, apiKey }: ImpactAnalyzerProps)

const getRiskColor = (risk: string) => {
switch (risk) {
case 'high': return 'text-red-600 bg-red-50 border-red-200'
case 'medium': return 'text-amber-600 bg-amber-50 border-amber-200'
case 'low': return 'text-green-600 bg-green-50 border-green-200'
default: return 'text-gray-600 bg-gray-50 border-gray-200'
case 'high': return 'text-red-400 bg-red-500/10 border-red-500/20'
case 'medium': return 'text-yellow-400 bg-yellow-500/10 border-yellow-500/20'
case 'low': return 'text-green-400 bg-green-500/10 border-green-500/20'
default: return 'text-gray-400 bg-white/5 border-white/10'
}
}

return (
<div className="space-y-6">
<div className="p-6 space-y-6">
{/* Input Form */}
<div className="card p-6">
<h3 className="text-base font-semibold mb-4 text-gray-900">Analyze Change Impact</h3>
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5">
<h3 className="text-base font-semibold mb-4 text-white">Analyze Change Impact</h3>
<form onSubmit={analyzeImpact} className="space-y-4">
<div>
<label className="block text-sm font-medium mb-2 text-gray-700">
<label className="block text-sm font-medium mb-2 text-gray-300">
File Path (relative to repository root)
</label>
<input
type="text"
value={filePath}
onChange={(e) => setFilePath(e.target.value)}
placeholder="e.g., src/auth/middleware.py or components/Button.tsx"
className="input"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-gray-500 focus:outline-none focus:border-blue-500/50 focus:ring-1 focus:ring-blue-500/20 transition-all"
disabled={loading}
/>
<p className="mt-1.5 text-xs text-gray-500">
<p className="mt-2 text-xs text-gray-500">
Enter the path of the file you want to modify to see its impact
</p>
</div>

<button
type="submit"
className="btn-primary"
className="px-4 py-2.5 bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white font-medium rounded-lg transition-all disabled:opacity-50"
disabled={loading}
>
{loading ? 'Analyzing...' : 'Analyze Impact'}
</button>
</form>

{error && (
<div className="mt-4 p-4 bg-red-50 border border-red-200 rounded-md text-sm text-red-700">
<div className="mt-4 p-4 bg-red-500/10 border border-red-500/20 rounded-lg text-sm text-red-400">
{error}
</div>
)}
Expand All @@ -110,46 +110,46 @@ export function ImpactAnalyzer({ repoId, apiUrl, apiKey }: ImpactAnalyzerProps)
{result && (
<div className="space-y-6">
{/* Risk Assessment */}
<div className={`card p-6 border-2 ${getRiskColor(result.risk_level)}`}>
<div className={`bg-[#0a0a0c] rounded-xl p-5 border-2 ${getRiskColor(result.risk_level)}`}>
<div className="flex items-center justify-between mb-3">
<h3 className="text-lg font-semibold">Risk Assessment</h3>
<span className={`px-4 py-1.5 rounded-md font-semibold uppercase text-sm border ${getRiskColor(result.risk_level)}`}>
<h3 className="text-lg font-semibold text-white">Risk Assessment</h3>
<span className={`px-4 py-1.5 rounded-lg font-semibold uppercase text-sm border ${getRiskColor(result.risk_level)}`}>
{result.risk_level} Risk
</span>
</div>
<p className="text-sm font-mono text-gray-700 mb-4">
<p className="text-sm font-mono text-gray-300 mb-4">
{result.file}
</p>
<p className="text-sm text-gray-600">
<p className="text-sm text-gray-400">
{result.impact_summary}
</p>
</div>

{/* Impact Overview */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="card p-5">
<div className="text-sm text-gray-600 mb-1">Direct Dependencies</div>
<div className="text-3xl font-bold text-blue-600">
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5">
<div className="text-sm text-gray-400 mb-1">Direct Dependencies</div>
<div className="text-3xl font-bold text-blue-400">
{result.dependency_count}
</div>
<div className="text-xs text-gray-500 mt-1">
Files this imports
</div>
</div>

<div className="card p-5">
<div className="text-sm text-gray-600 mb-1">Total Impact</div>
<div className="text-3xl font-bold text-amber-600">
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5">
<div className="text-sm text-gray-400 mb-1">Total Impact</div>
<div className="text-3xl font-bold text-yellow-400">
{result.dependent_count}
</div>
<div className="text-xs text-gray-500 mt-1">
Files affected by changes
</div>
</div>

<div className="card p-5">
<div className="text-sm text-gray-600 mb-1">Test Files</div>
<div className="text-3xl font-bold text-green-600">
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5">
<div className="text-sm text-gray-400 mb-1">Test Files</div>
<div className="text-3xl font-bold text-green-400">
{result.test_files?.length || 0}
</div>
<div className="text-xs text-gray-500 mt-1">
Expand All @@ -160,16 +160,16 @@ export function ImpactAnalyzer({ repoId, apiUrl, apiKey }: ImpactAnalyzerProps)

{/* Dependencies (What This File Needs) */}
{result.direct_dependencies && result.direct_dependencies.length > 0 && (
<div className="card p-6">
<h3 className="text-base font-semibold mb-4 text-gray-900">
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5">
<h3 className="text-base font-semibold mb-4 text-white">
Dependencies ({result.direct_dependencies.length})
</h3>
<p className="text-sm text-gray-600 mb-3">
<p className="text-sm text-gray-400 mb-3">
Files this file imports (upstream)
</p>
<div className="space-y-1.5">
{result.direct_dependencies.map((dep, idx) => (
<div key={idx} className="text-sm font-mono text-gray-700 bg-blue-50 px-3 py-2 rounded">
<div key={idx} className="text-sm font-mono text-gray-300 bg-blue-500/10 border border-blue-500/20 px-3 py-2 rounded-lg">
{dep}
</div>
))}
Expand All @@ -179,16 +179,16 @@ export function ImpactAnalyzer({ repoId, apiUrl, apiKey }: ImpactAnalyzerProps)

{/* Dependents (What Breaks If Changed) */}
{result.all_dependents && result.all_dependents.length > 0 && (
<div className="card p-6">
<h3 className="text-base font-semibold mb-4 text-gray-900">
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5">
<h3 className="text-base font-semibold mb-4 text-white">
Affected Files ({result.all_dependents.length})
</h3>
<p className="text-sm text-gray-600 mb-3">
<p className="text-sm text-gray-400 mb-3">
Files that would be impacted by changes to this file (downstream)
</p>
<div className="space-y-1.5 max-h-96 overflow-y-auto">
{result.all_dependents.map((dep, idx) => (
<div key={idx} className="text-sm font-mono text-gray-700 bg-amber-50 px-3 py-2 rounded">
<div key={idx} className="text-sm font-mono text-gray-300 bg-yellow-500/10 border border-yellow-500/20 px-3 py-2 rounded-lg">
{dep}
</div>
))}
Expand All @@ -198,16 +198,16 @@ export function ImpactAnalyzer({ repoId, apiUrl, apiKey }: ImpactAnalyzerProps)

{/* Test Files */}
{result.test_files && result.test_files.length > 0 && (
<div className="card p-6">
<h3 className="text-base font-semibold mb-4 text-gray-900">
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5">
<h3 className="text-base font-semibold mb-4 text-white">
Related Tests ({result.test_files.length})
</h3>
<p className="text-sm text-gray-600 mb-3">
<p className="text-sm text-gray-400 mb-3">
Test files that may need updates
</p>
<div className="space-y-1.5">
{result.test_files.map((test, idx) => (
<div key={idx} className="text-sm font-mono text-gray-700 bg-green-50 px-3 py-2 rounded">
<div key={idx} className="text-sm font-mono text-gray-300 bg-green-500/10 border border-green-500/20 px-3 py-2 rounded-lg">
{test}
</div>
))}
Expand Down
Loading
Loading