Skip to content

Commit 5604d28

Browse files
authored
Merge pull request #228 from DevanshuNEU/feat/dependency-graph-phase2
feat(dependency-graph): Phase 2 - Directory clustering with expand/collapse
2 parents 78216dd + 9a6a2ae commit 5604d28

4 files changed

Lines changed: 410 additions & 72 deletions

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { memo } from 'react'
2+
import { Handle, Position } from 'reactflow'
3+
import type { NodeProps } from 'reactflow'
4+
import { Folder, FolderOpen, ChevronRight } from 'lucide-react'
5+
import { cn } from '@/lib/utils'
6+
import { Badge } from '@/components/ui/badge'
7+
import type { RiskLevel } from './hooks/useImpactAnalysis'
8+
9+
export interface DirectoryNodeData {
10+
label: string
11+
fullPath: string
12+
fileCount: number
13+
totalDependents: number
14+
maxRisk: RiskLevel
15+
isExpanded: boolean
16+
state: 'default' | 'selected' | 'direct' | 'transitive' | 'dimmed'
17+
}
18+
19+
const STATE_STYLES: Record<DirectoryNodeData['state'], string> = {
20+
default: 'border-zinc-300 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-800/90',
21+
selected: 'border-indigo-500 bg-indigo-50 dark:bg-indigo-950 ring-2 ring-indigo-500/50 shadow-lg shadow-indigo-500/20',
22+
direct: 'border-rose-500 bg-rose-50 dark:bg-rose-950 ring-1 ring-rose-500/30',
23+
transitive: 'border-amber-500 bg-amber-50 dark:bg-amber-950 ring-1 ring-amber-500/30',
24+
dimmed: 'border-zinc-200 bg-zinc-100/50 opacity-40 dark:border-zinc-800 dark:bg-zinc-900/50',
25+
}
26+
27+
const RISK_STYLES: Record<RiskLevel, string> = {
28+
low: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400',
29+
medium: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-500/10 dark:text-yellow-400',
30+
high: 'bg-orange-100 text-orange-700 dark:bg-orange-500/10 dark:text-orange-400',
31+
critical: 'bg-rose-100 text-rose-700 dark:bg-rose-500/10 dark:text-rose-400',
32+
}
33+
34+
function DirectoryNodeComponent({ data }: NodeProps<DirectoryNodeData>) {
35+
const stateStyle = STATE_STYLES[data.state]
36+
const FolderIcon = data.isExpanded ? FolderOpen : Folder
37+
38+
return (
39+
<>
40+
<Handle
41+
type="target"
42+
position={Position.Left}
43+
className="!bg-zinc-400 dark:!bg-zinc-600 !w-2 !h-2 !border-0"
44+
/>
45+
46+
<div
47+
className={cn(
48+
'px-3 py-2.5 rounded-lg border-2 min-w-[180px]',
49+
'transition-all duration-200 ease-out',
50+
'hover:scale-[1.02] hover:shadow-md active:scale-[0.98]',
51+
'cursor-pointer select-none',
52+
stateStyle
53+
)}
54+
>
55+
<div className="flex items-center gap-2 mb-1">
56+
<FolderIcon className="w-4 h-4 text-amber-500 dark:text-amber-400 flex-shrink-0" />
57+
<span
58+
className="font-semibold text-sm text-zinc-800 dark:text-zinc-100 truncate flex-1"
59+
title={data.fullPath}
60+
>
61+
{data.label}/
62+
</span>
63+
<ChevronRight className={cn(
64+
'w-3.5 h-3.5 text-zinc-400 transition-transform duration-200 ease-out',
65+
data.isExpanded && 'rotate-90'
66+
)} />
67+
</div>
68+
69+
<div className="flex items-center gap-3 text-[11px] text-zinc-500 dark:text-zinc-400">
70+
<span className="font-medium">
71+
{data.fileCount} file{data.fileCount !== 1 ? 's' : ''}
72+
</span>
73+
<span></span>
74+
<span className={cn(
75+
'font-medium',
76+
data.totalDependents >= 30 ? 'text-rose-600 dark:text-rose-400' :
77+
data.totalDependents >= 10 ? 'text-amber-600 dark:text-amber-400' :
78+
'text-zinc-500 dark:text-zinc-400'
79+
)}>
80+
{data.totalDependents} deps
81+
</span>
82+
{data.maxRisk !== 'low' && (
83+
<Badge variant="secondary" className={cn('text-[10px] px-1.5 py-0 h-5 ml-auto', RISK_STYLES[data.maxRisk])}>
84+
{data.maxRisk === 'critical' ? 'Crit' : data.maxRisk === 'high' ? 'High' : 'Med'}
85+
</Badge>
86+
)}
87+
</div>
88+
</div>
89+
90+
<Handle
91+
type="source"
92+
position={Position.Right}
93+
className="!bg-zinc-400 dark:!bg-zinc-600 !w-2 !h-2 !border-0"
94+
/>
95+
</>
96+
)
97+
}
98+
99+
export const DirectoryNode = memo(DirectoryNodeComponent)

frontend/src/components/DependencyGraph/GraphToolbar.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { memo } from 'react'
2-
import { RotateCcw, Maximize2, Filter, Eye, EyeOff } from 'lucide-react'
2+
import { RotateCcw, Maximize2, Filter, Eye, EyeOff, FolderTree } from 'lucide-react'
33
import { cn } from '@/lib/utils'
44
import { Button } from '@/components/ui/button'
55

@@ -8,8 +8,10 @@ interface GraphToolbarProps {
88
visibleFiles: number
99
showAll: boolean
1010
showTests: boolean
11+
clusterByDir: boolean
1112
onToggleShowAll: () => void
1213
onToggleTests: () => void
14+
onToggleCluster: () => void
1315
onResetView: () => void
1416
onFullscreen?: () => void
1517
}
@@ -19,8 +21,10 @@ function GraphToolbarComponent({
1921
visibleFiles,
2022
showAll,
2123
showTests,
24+
clusterByDir,
2225
onToggleShowAll,
2326
onToggleTests,
27+
onToggleCluster,
2428
onResetView,
2529
onFullscreen,
2630
}: GraphToolbarProps) {
@@ -37,21 +41,37 @@ function GraphToolbarComponent({
3741
</div>
3842

3943
<div className="flex items-center gap-2">
44+
<Button
45+
variant={clusterByDir ? 'default' : 'secondary'}
46+
size="sm"
47+
onClick={onToggleCluster}
48+
className="h-8"
49+
aria-pressed={clusterByDir}
50+
title={clusterByDir ? 'Click to show flat view' : 'Group files by directory'}
51+
>
52+
<FolderTree className="w-3.5 h-3.5 mr-1.5" />
53+
Cluster
54+
</Button>
55+
4056
<Button
4157
variant={showAll ? 'default' : 'secondary'}
4258
size="sm"
4359
onClick={onToggleShowAll}
4460
className="h-8"
61+
aria-pressed={showAll}
62+
title={showAll ? 'Show top 15 files' : 'Show all files'}
4563
>
4664
<Filter className="w-3.5 h-3.5 mr-1.5" />
4765
{showAll ? 'Show Top 15' : 'Show All'}
4866
</Button>
4967

5068
<Button
51-
variant="secondary"
69+
variant={showTests ? 'secondary' : 'outline'}
5270
size="sm"
5371
onClick={onToggleTests}
54-
className={cn('h-8', !showTests && 'opacity-60')}
72+
className={cn('h-8', !showTests && 'opacity-70 line-through')}
73+
aria-pressed={showTests}
74+
title={showTests ? 'Click to hide test files' : 'Click to show test files'}
5575
>
5676
{showTests ? <Eye className="w-3.5 h-3.5 mr-1.5" /> : <EyeOff className="w-3.5 h-3.5 mr-1.5" />}
5777
Tests

0 commit comments

Comments
 (0)