@checkstack/dependency-frontend 0.2.17 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @checkstack/dependency-frontend
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 35463ef: Improve dependency map directional clarity
|
|
8
|
+
|
|
9
|
+
- Redesigned system nodes with a split footer bar showing directional dependency counts (`← N used by | depends N →`), making each node self-documenting
|
|
10
|
+
- Color-coded connection handles: teal for incoming ("used by") and violet for outgoing ("depends on")
|
|
11
|
+
- Fixed invisible edge arrows by implementing custom SVG marker definitions with impact-type-matched colors (sky for informational, amber for degraded, red for critical)
|
|
12
|
+
- Updated the legend panel to explain handle colors alongside the existing impact type guide
|
|
13
|
+
|
|
14
|
+
## 0.2.18
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [298bf42]
|
|
19
|
+
- @checkstack/catalog-common@1.5.0
|
|
20
|
+
- @checkstack/dashboard-frontend@0.4.5
|
|
21
|
+
|
|
3
22
|
## 0.2.17
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/dependency-frontend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.tsx",
|
|
6
6
|
"checkstack": {
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"lint:code": "eslint . --max-warnings 0"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@checkstack/catalog-common": "1.4.
|
|
15
|
+
"@checkstack/catalog-common": "1.4.1",
|
|
16
16
|
"@checkstack/common": "0.6.5",
|
|
17
|
-
"@checkstack/dashboard-frontend": "0.4.
|
|
17
|
+
"@checkstack/dashboard-frontend": "0.4.4",
|
|
18
18
|
"@checkstack/dependency-common": "0.2.1",
|
|
19
19
|
"@checkstack/frontend-api": "0.3.9",
|
|
20
20
|
"@checkstack/healthcheck-common": "0.11.0",
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
ReactFlowProvider,
|
|
11
11
|
type NodeChange,
|
|
12
12
|
type Connection,
|
|
13
|
-
MarkerType,
|
|
14
13
|
Panel,
|
|
15
14
|
} from "@xyflow/react";
|
|
16
15
|
import "@xyflow/react/dist/style.css";
|
|
@@ -247,6 +246,21 @@ function DependencyMapContent() {
|
|
|
247
246
|
const savedPositions = posData?.positions ?? [];
|
|
248
247
|
const warnings = warningsData?.warnings ?? {};
|
|
249
248
|
const healthStatuses = healthData?.statuses ?? {};
|
|
249
|
+
const deps = depsData?.dependencies ?? [];
|
|
250
|
+
|
|
251
|
+
// Compute per-system dependency counts
|
|
252
|
+
const upstreamCountMap = new Map<string, number>();
|
|
253
|
+
const downstreamCountMap = new Map<string, number>();
|
|
254
|
+
for (const dep of deps) {
|
|
255
|
+
upstreamCountMap.set(
|
|
256
|
+
dep.sourceSystemId,
|
|
257
|
+
(upstreamCountMap.get(dep.sourceSystemId) ?? 0) + 1,
|
|
258
|
+
);
|
|
259
|
+
downstreamCountMap.set(
|
|
260
|
+
dep.targetSystemId,
|
|
261
|
+
(downstreamCountMap.get(dep.targetSystemId) ?? 0) + 1,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
250
264
|
|
|
251
265
|
// Lookup maps for position resolution
|
|
252
266
|
const savedPositionMap = new Map(
|
|
@@ -287,6 +301,8 @@ function DependencyMapContent() {
|
|
|
287
301
|
systemId: system.id,
|
|
288
302
|
status: selfStatus,
|
|
289
303
|
derivedState: warning?.derivedState,
|
|
304
|
+
upstreamCount: upstreamCountMap.get(system.id) ?? 0,
|
|
305
|
+
downstreamCount: downstreamCountMap.get(system.id) ?? 0,
|
|
290
306
|
};
|
|
291
307
|
|
|
292
308
|
return {
|
|
@@ -298,7 +314,7 @@ function DependencyMapContent() {
|
|
|
298
314
|
});
|
|
299
315
|
|
|
300
316
|
setNodes(newNodes);
|
|
301
|
-
}, [systemsData, posData, warningsData, healthData, setNodes]);
|
|
317
|
+
}, [systemsData, posData, warningsData, healthData, depsData, setNodes]);
|
|
302
318
|
|
|
303
319
|
// Build edges separately — only depends on dependency data
|
|
304
320
|
useEffect(() => {
|
|
@@ -318,11 +334,6 @@ function DependencyMapContent() {
|
|
|
318
334
|
target: dep.targetSystemId,
|
|
319
335
|
type: "dependency" as const,
|
|
320
336
|
animated: dep.transitive,
|
|
321
|
-
markerEnd: {
|
|
322
|
-
type: MarkerType.ArrowClosed,
|
|
323
|
-
width: 16,
|
|
324
|
-
height: 16,
|
|
325
|
-
},
|
|
326
337
|
data: edgeData,
|
|
327
338
|
};
|
|
328
339
|
},
|
|
@@ -502,7 +513,28 @@ function DependencyMapContent() {
|
|
|
502
513
|
<Panel position="bottom-left">
|
|
503
514
|
<div className="bg-card/90 backdrop-blur-sm border border-border rounded-lg p-3 shadow-lg max-w-64">
|
|
504
515
|
<p className="text-xs font-semibold text-muted-foreground mb-2 uppercase tracking-wider">
|
|
505
|
-
|
|
516
|
+
Legend
|
|
517
|
+
</p>
|
|
518
|
+
|
|
519
|
+
{/* Direction legend */}
|
|
520
|
+
<div className="space-y-1.5 mb-3">
|
|
521
|
+
<div className="flex items-center gap-2">
|
|
522
|
+
<div className="w-3 h-3 rounded-full bg-teal-400/60 border-2 border-background shrink-0" />
|
|
523
|
+
<span className="text-xs text-muted-foreground">
|
|
524
|
+
Used by (incoming)
|
|
525
|
+
</span>
|
|
526
|
+
</div>
|
|
527
|
+
<div className="flex items-center gap-2">
|
|
528
|
+
<div className="w-3 h-3 rounded-full bg-violet-400/60 border-2 border-background shrink-0" />
|
|
529
|
+
<span className="text-xs text-muted-foreground">
|
|
530
|
+
Depends on (outgoing)
|
|
531
|
+
</span>
|
|
532
|
+
</div>
|
|
533
|
+
</div>
|
|
534
|
+
|
|
535
|
+
{/* Impact legend */}
|
|
536
|
+
<p className="text-xs font-semibold text-muted-foreground mb-2 uppercase tracking-wider">
|
|
537
|
+
Impact
|
|
506
538
|
</p>
|
|
507
539
|
<div className="space-y-1.5">
|
|
508
540
|
<div className="flex items-center gap-2">
|
|
@@ -27,9 +27,17 @@ const impactStrokeWidths: Record<ImpactType, number> = {
|
|
|
27
27
|
critical: 2.5,
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
/** Hex colors for SVG marker fills — must match the Tailwind stroke classes. */
|
|
31
|
+
const impactHexColors: Record<ImpactType, string> = {
|
|
32
|
+
informational: "#38bdf8", // sky-400
|
|
33
|
+
degraded: "#fbbf24", // amber-400
|
|
34
|
+
critical: "#f87171", // red-400
|
|
35
|
+
};
|
|
36
|
+
|
|
30
37
|
/**
|
|
31
38
|
* Custom React Flow edge displaying dependency impact type via color + thickness.
|
|
32
|
-
* Transitive edges use dashed stroke.
|
|
39
|
+
* Transitive edges use dashed stroke. Renders a visible arrowhead marker
|
|
40
|
+
* colored to match the impact type.
|
|
33
41
|
*/
|
|
34
42
|
export function DependencyEdgeComponent({
|
|
35
43
|
id,
|
|
@@ -55,9 +63,32 @@ export function DependencyEdgeComponent({
|
|
|
55
63
|
const isTransitive = data?.transitive ?? false;
|
|
56
64
|
const colorClass = impactColors[impactType];
|
|
57
65
|
const strokeWidth = impactStrokeWidths[impactType];
|
|
66
|
+
const hexColor = selected ? "hsl(var(--primary))" : impactHexColors[impactType];
|
|
67
|
+
|
|
68
|
+
// Unique marker ID per edge to avoid color clashes between different impact types
|
|
69
|
+
const markerId = `arrow-${id}`;
|
|
58
70
|
|
|
59
71
|
return (
|
|
60
72
|
<>
|
|
73
|
+
{/* SVG marker definition for the arrowhead */}
|
|
74
|
+
<defs>
|
|
75
|
+
<marker
|
|
76
|
+
id={markerId}
|
|
77
|
+
markerWidth="12"
|
|
78
|
+
markerHeight="12"
|
|
79
|
+
refX="10"
|
|
80
|
+
refY="6"
|
|
81
|
+
orient="auto"
|
|
82
|
+
markerUnits="userSpaceOnUse"
|
|
83
|
+
>
|
|
84
|
+
<path
|
|
85
|
+
d="M2,2 L10,6 L2,10 L4,6 Z"
|
|
86
|
+
fill={hexColor}
|
|
87
|
+
opacity={selected ? 1 : 0.8}
|
|
88
|
+
/>
|
|
89
|
+
</marker>
|
|
90
|
+
</defs>
|
|
91
|
+
|
|
61
92
|
<BaseEdge
|
|
62
93
|
id={id}
|
|
63
94
|
path={edgePath}
|
|
@@ -65,6 +96,7 @@ export function DependencyEdgeComponent({
|
|
|
65
96
|
style={{
|
|
66
97
|
strokeWidth: selected ? strokeWidth + 1 : strokeWidth,
|
|
67
98
|
strokeDasharray: isTransitive ? "6 4" : undefined,
|
|
99
|
+
markerEnd: `url(#${markerId})`,
|
|
68
100
|
}}
|
|
69
101
|
/>
|
|
70
102
|
|
|
@@ -6,6 +6,10 @@ export interface SystemNodeData extends Record<string, unknown> {
|
|
|
6
6
|
status?: "operational" | "degraded" | "down";
|
|
7
7
|
derivedState?: "info" | "degraded" | "down";
|
|
8
8
|
systemId: string;
|
|
9
|
+
/** Number of systems this node depends on (outgoing edges via right handle) */
|
|
10
|
+
upstreamCount: number;
|
|
11
|
+
/** Number of systems that depend on this node (incoming edges via left handle) */
|
|
12
|
+
downstreamCount: number;
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
export type SystemNode = Node<SystemNodeData, "system">;
|
|
@@ -48,6 +52,8 @@ function combineStatus(
|
|
|
48
52
|
/**
|
|
49
53
|
* Custom React Flow node representing a system in the dependency graph.
|
|
50
54
|
* Color-coded by worst of own status and derived warning state.
|
|
55
|
+
* Features a split footer bar showing directional dependency counts
|
|
56
|
+
* and color-coded handles for clear directionality.
|
|
51
57
|
*/
|
|
52
58
|
export const SystemNodeComponent = memo(function SystemNodeComponent({
|
|
53
59
|
data,
|
|
@@ -55,87 +61,122 @@ export const SystemNodeComponent = memo(function SystemNodeComponent({
|
|
|
55
61
|
}: NodeProps<SystemNode>) {
|
|
56
62
|
const effectiveStatus = combineStatus(data.status, data.derivedState);
|
|
57
63
|
const styles = statusStyles[effectiveStatus];
|
|
64
|
+
const hasConnections = data.upstreamCount > 0 || data.downstreamCount > 0;
|
|
58
65
|
|
|
59
66
|
return (
|
|
60
67
|
<>
|
|
61
|
-
{/* Target handle (left) — "I am depended upon" */}
|
|
68
|
+
{/* Target handle (left) — "I am depended upon" — teal */}
|
|
62
69
|
<Handle
|
|
63
70
|
type="target"
|
|
64
71
|
position={Position.Left}
|
|
65
|
-
className="!w-
|
|
72
|
+
className="!w-3 !h-3 !bg-teal-400/60 !border-2 !border-background !z-10"
|
|
66
73
|
/>
|
|
67
74
|
|
|
68
75
|
<div
|
|
69
76
|
className={`
|
|
70
|
-
|
|
77
|
+
rounded-xl border-2 shadow-lg backdrop-blur-sm
|
|
71
78
|
transition-all duration-200
|
|
72
79
|
${styles.border} ${styles.bg} ${styles.glow}
|
|
73
80
|
${selected ? "ring-2 ring-primary ring-offset-2 ring-offset-background" : ""}
|
|
74
81
|
hover:scale-[1.02] cursor-grab active:cursor-grabbing
|
|
75
|
-
min-w-[140px] max-w-[
|
|
82
|
+
min-w-[140px] max-w-[220px] overflow-hidden
|
|
76
83
|
`}
|
|
77
84
|
>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<div className="
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
/>
|
|
84
|
-
{effectiveStatus !== "operational" && (
|
|
85
|
+
{/* Main body */}
|
|
86
|
+
<div className="px-4 py-3">
|
|
87
|
+
<div className="flex items-center gap-2">
|
|
88
|
+
{/* Status dot */}
|
|
89
|
+
<div className="relative flex-shrink-0">
|
|
85
90
|
<div
|
|
86
|
-
className={`
|
|
91
|
+
className={`w-2.5 h-2.5 rounded-full ${styles.dot}`}
|
|
87
92
|
/>
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
{effectiveStatus !== "operational" && (
|
|
94
|
+
<div
|
|
95
|
+
className={`absolute inset-0 w-2.5 h-2.5 rounded-full ${styles.dot} animate-ping opacity-75`}
|
|
96
|
+
/>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
90
99
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
{/* System name */}
|
|
101
|
+
<span className="text-sm font-medium text-foreground truncate">
|
|
102
|
+
{data.label}
|
|
103
|
+
</span>
|
|
104
|
+
</div>
|
|
96
105
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
<span
|
|
100
|
-
className={`text-[10px] uppercase tracking-wider font-semibold ${
|
|
101
|
-
(data.status ?? "operational") === "operational"
|
|
102
|
-
? "text-emerald-500/70"
|
|
103
|
-
: (data.status ?? "operational") === "degraded"
|
|
104
|
-
? "text-amber-500/70"
|
|
105
|
-
: "text-red-500/70"
|
|
106
|
-
}`}
|
|
107
|
-
>
|
|
108
|
-
{(data.status ?? "operational") === "operational"
|
|
109
|
-
? "Healthy"
|
|
110
|
-
: (data.status ?? "operational") === "degraded"
|
|
111
|
-
? "Degraded"
|
|
112
|
-
: "Unhealthy"}
|
|
113
|
-
</span>
|
|
114
|
-
{data.derivedState && (
|
|
106
|
+
{/* Status labels */}
|
|
107
|
+
<div className="mt-1.5 flex flex-col gap-0.5">
|
|
115
108
|
<span
|
|
116
109
|
className={`text-[10px] uppercase tracking-wider font-semibold ${
|
|
117
|
-
data.
|
|
118
|
-
? "text-
|
|
119
|
-
: data.
|
|
110
|
+
(data.status ?? "operational") === "operational"
|
|
111
|
+
? "text-emerald-500/70"
|
|
112
|
+
: (data.status ?? "operational") === "degraded"
|
|
120
113
|
? "text-amber-500/70"
|
|
121
114
|
: "text-red-500/70"
|
|
122
115
|
}`}
|
|
123
116
|
>
|
|
124
|
-
{data.
|
|
125
|
-
? "
|
|
126
|
-
: data.
|
|
127
|
-
? "
|
|
128
|
-
: "
|
|
117
|
+
{(data.status ?? "operational") === "operational"
|
|
118
|
+
? "Healthy"
|
|
119
|
+
: (data.status ?? "operational") === "degraded"
|
|
120
|
+
? "Degraded"
|
|
121
|
+
: "Unhealthy"}
|
|
129
122
|
</span>
|
|
130
|
-
|
|
123
|
+
{data.derivedState && (
|
|
124
|
+
<span
|
|
125
|
+
className={`text-[10px] uppercase tracking-wider font-semibold ${
|
|
126
|
+
data.derivedState === "info"
|
|
127
|
+
? "text-blue-400/70"
|
|
128
|
+
: data.derivedState === "degraded"
|
|
129
|
+
? "text-amber-500/70"
|
|
130
|
+
: "text-red-500/70"
|
|
131
|
+
}`}
|
|
132
|
+
>
|
|
133
|
+
{data.derivedState === "info"
|
|
134
|
+
? "↑ Upstream issue"
|
|
135
|
+
: data.derivedState === "degraded"
|
|
136
|
+
? "↑ Upstream degraded"
|
|
137
|
+
: "↑ Upstream down"}
|
|
138
|
+
</span>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
131
141
|
</div>
|
|
142
|
+
|
|
143
|
+
{/* Directional footer — only shown when the node has connections */}
|
|
144
|
+
{hasConnections && (
|
|
145
|
+
<div className="flex border-t border-border/40">
|
|
146
|
+
{/* Left zone: incoming — systems that depend on this node */}
|
|
147
|
+
<div className="flex-1 flex items-center gap-1 px-2.5 py-1.5 bg-teal-500/5 border-r border-border/30">
|
|
148
|
+
<span className="text-[10px] text-teal-400/80 font-medium">
|
|
149
|
+
←
|
|
150
|
+
</span>
|
|
151
|
+
<span className="text-[10px] text-teal-400/80 font-medium tabular-nums">
|
|
152
|
+
{data.downstreamCount}
|
|
153
|
+
</span>
|
|
154
|
+
<span className="text-[10px] text-muted-foreground/60 truncate">
|
|
155
|
+
used by
|
|
156
|
+
</span>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
{/* Right zone: outgoing — systems this node depends on */}
|
|
160
|
+
<div className="flex-1 flex items-center justify-end gap-1 px-2.5 py-1.5 bg-violet-500/5">
|
|
161
|
+
<span className="text-[10px] text-muted-foreground/60 truncate">
|
|
162
|
+
depends
|
|
163
|
+
</span>
|
|
164
|
+
<span className="text-[10px] text-violet-400/80 font-medium tabular-nums">
|
|
165
|
+
{data.upstreamCount}
|
|
166
|
+
</span>
|
|
167
|
+
<span className="text-[10px] text-violet-400/80 font-medium">
|
|
168
|
+
→
|
|
169
|
+
</span>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
132
173
|
</div>
|
|
133
174
|
|
|
134
|
-
{/* Source handle (right) — "I depend on..." */}
|
|
175
|
+
{/* Source handle (right) — "I depend on..." — violet */}
|
|
135
176
|
<Handle
|
|
136
177
|
type="source"
|
|
137
178
|
position={Position.Right}
|
|
138
|
-
className="!w-
|
|
179
|
+
className="!w-3 !h-3 !bg-violet-400/60 !border-2 !border-background !z-10"
|
|
139
180
|
/>
|
|
140
181
|
</>
|
|
141
182
|
);
|