@c2l2c/backstage-plugin-dora-metrics 0.1.0 → 0.3.2

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.
@@ -1,5 +1,4 @@
1
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { useState, useEffect, useRef } from 'react';
1
+ import React, { useState, useEffect, useRef } from 'react';
3
2
  import { useTheme } from '@material-ui/core/styles';
4
3
  import { useApi } from '@backstage/core-plugin-api';
5
4
  import { useEntity } from '@backstage/plugin-catalog-react';
@@ -11,6 +10,16 @@ import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '.
11
10
  import { ExternalLink } from 'lucide-react';
12
11
 
13
12
  const ANNOTATION_PROJECT_SLUG = "github.com/project-slug";
13
+ const ANNOTATION_ENVIRONMENTS = "dora-metrics/environments";
14
+ const ANNOTATION_TARGETS = "dora-metrics/targets";
15
+ function parseAnnotationJson(raw) {
16
+ if (!raw) return void 0;
17
+ try {
18
+ return JSON.parse(raw);
19
+ } catch {
20
+ return void 0;
21
+ }
22
+ }
14
23
  const DATE_RANGE_OPTIONS = [
15
24
  { value: "7", label: "Last 7 days" },
16
25
  { value: "14", label: "Last 14 days" },
@@ -60,7 +69,7 @@ const LOADING_MESSAGES = [
60
69
  const STAGES = ["FETCH", "PARSE", "SCORE", "RENDER"];
61
70
  const FONT_IMPACT = '"Impact","Haettenschweiler","Franklin Gothic Heavy","Arial Black",sans-serif';
62
71
  const FONT_MONO = '"JetBrains Mono","Fira Code","Consolas",monospace';
63
- function DojoLoadingOverlay() {
72
+ function DoraLoadingOverlay() {
64
73
  const theme = useTheme();
65
74
  const isDark = theme.palette.type === "dark";
66
75
  const [activeStage, setActiveStage] = useState(0);
@@ -81,138 +90,110 @@ function DojoLoadingOverlay() {
81
90
  const mutedColor = isDark ? "#3f3f46" : "#a1a1aa";
82
91
  const subtitleColor = isDark ? "#52525b" : "#71717a";
83
92
  const borderColor = isDark ? "rgba(255,255,255,0.07)" : "rgba(0,0,0,0.08)";
84
- return /* @__PURE__ */ jsxs(Fragment, { children: [
85
- /* @__PURE__ */ jsx("div", { "aria-hidden": true, style: {
86
- position: "fixed",
93
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { "aria-hidden": true, style: {
94
+ position: "fixed",
95
+ inset: 0,
96
+ zIndex: 900,
97
+ backdropFilter: "blur(12px)",
98
+ WebkitBackdropFilter: "blur(12px)",
99
+ background: isDark ? "rgba(6,6,9,0.65)" : "rgba(253,252,251,0.70)"
100
+ } }), /* @__PURE__ */ React.createElement("div", { style: {
101
+ position: "fixed",
102
+ zIndex: 901,
103
+ top: "50%",
104
+ left: "50%",
105
+ transform: "translateX(-50%) translateY(-50%)",
106
+ display: "flex",
107
+ flexDirection: "column",
108
+ alignItems: "center",
109
+ padding: "48px 56px 40px",
110
+ borderRadius: 20,
111
+ background: isDark ? "rgba(9,9,12,0.82)" : "rgba(255,255,255,0.88)",
112
+ border: `1px solid ${borderColor}`,
113
+ boxShadow: isDark ? "0 0 0 1px rgba(250,100,0,0.06), 0 32px 80px rgba(0,0,0,0.65)" : "0 32px 80px rgba(0,0,0,0.12)",
114
+ backdropFilter: "blur(20px)",
115
+ WebkitBackdropFilter: "blur(20px)",
116
+ animation: "dlsCardIn 0.35s cubic-bezier(0.34,1.2,0.64,1) both",
117
+ overflow: "visible"
118
+ } }, /* @__PURE__ */ React.createElement("div", { "aria-hidden": true, style: {
119
+ position: "absolute",
120
+ width: 500,
121
+ height: 360,
122
+ left: "50%",
123
+ top: "50%",
124
+ transform: "translateX(-50%) translateY(-50%)",
125
+ background: `radial-gradient(ellipse at center, ${isDark ? "rgba(250,100,0,0.08)" : "rgba(250,100,0,0.05)"} 0%, transparent 70%)`,
126
+ animation: "dlsBreath 3.2s ease-in-out infinite",
127
+ pointerEvents: "none",
128
+ borderRadius: 20
129
+ } }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", lineHeight: 1, marginBottom: 6 } }, ["D", "O", "R", "A"].map((letter, i) => /* @__PURE__ */ React.createElement("span", { key: i, style: {
130
+ fontFamily: FONT_IMPACT,
131
+ fontWeight: 900,
132
+ fontSize: 110,
133
+ color: "#FA6400",
134
+ letterSpacing: "0.04em",
135
+ display: "inline-block",
136
+ transform: "skewX(-6deg)",
137
+ textShadow,
138
+ WebkitTextStroke: "0.5px #C24E00",
139
+ animation: `dlsLetterDrop 0.6s cubic-bezier(0.34,1.45,0.64,1) ${i * 0.1}s both`
140
+ } }, letter))), /* @__PURE__ */ React.createElement("span", { style: {
141
+ fontFamily: "Inter,system-ui,sans-serif",
142
+ fontSize: 13,
143
+ fontWeight: 400,
144
+ letterSpacing: "0.06em",
145
+ color: subtitleColor,
146
+ marginBottom: 32,
147
+ animation: "dlsFadeUp 0.4s ease 0.55s both"
148
+ } }, "Metrics"), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 0, marginBottom: 20, animation: "dlsFadeUp 0.4s ease 0.7s both" } }, STAGES.map((stage, i) => {
149
+ const isActive = i === activeStage;
150
+ const isComplete = i < activeStage;
151
+ return /* @__PURE__ */ React.createElement("div", { key: stage, style: { display: "flex", alignItems: "center", gap: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 5, width: 64 } }, /* @__PURE__ */ React.createElement("div", { style: { position: "relative", width: 12, height: 12 } }, isActive && /* @__PURE__ */ React.createElement("div", { style: {
152
+ position: "absolute",
153
+ inset: -6,
154
+ borderRadius: "50%",
155
+ border: "1.5px solid rgba(250,100,0,0.5)",
156
+ animation: "dlsPulse 1s ease-out infinite"
157
+ } }), /* @__PURE__ */ React.createElement("div", { style: {
158
+ width: 12,
159
+ height: 12,
160
+ borderRadius: "50%",
161
+ background: isActive || isComplete ? "#FA6400" : isDark ? "#27272a" : "#e4e4e7",
162
+ border: `2px solid ${isActive || isComplete ? "#C24E00" : isDark ? "#3f3f46" : "#d4d4d8"}`,
163
+ boxShadow: isActive ? "0 0 14px rgba(250,100,0,0.7)" : "none",
164
+ transition: "background 0.3s, box-shadow 0.3s"
165
+ } })), /* @__PURE__ */ React.createElement("span", { style: {
166
+ fontFamily: "Inter,system-ui,sans-serif",
167
+ fontSize: 8,
168
+ fontWeight: 700,
169
+ letterSpacing: "0.12em",
170
+ textTransform: "uppercase",
171
+ color: isActive ? "#FA6400" : isComplete ? mutedColor : mutedColor,
172
+ transition: "color 0.3s"
173
+ } }, stage)), i < STAGES.length - 1 && /* @__PURE__ */ React.createElement("div", { style: {
174
+ width: 32,
175
+ height: 2,
176
+ marginBottom: 14,
177
+ position: "relative",
178
+ overflow: "hidden",
179
+ background: isDark ? "#27272a" : "#e4e4e7",
180
+ borderRadius: 2
181
+ } }, /* @__PURE__ */ React.createElement("div", { style: {
182
+ position: "absolute",
87
183
  inset: 0,
88
- zIndex: 900,
89
- backdropFilter: "blur(12px)",
90
- WebkitBackdropFilter: "blur(12px)",
91
- background: isDark ? "rgba(6,6,9,0.65)" : "rgba(253,252,251,0.70)"
92
- } }),
93
- /* @__PURE__ */ jsxs("div", { style: {
94
- position: "fixed",
95
- zIndex: 901,
96
- top: "50%",
97
- left: "50%",
98
- transform: "translateX(-50%) translateY(-50%)",
99
- display: "flex",
100
- flexDirection: "column",
101
- alignItems: "center",
102
- padding: "48px 56px 40px",
103
- borderRadius: 20,
104
- background: isDark ? "rgba(9,9,12,0.82)" : "rgba(255,255,255,0.88)",
105
- border: `1px solid ${borderColor}`,
106
- boxShadow: isDark ? "0 0 0 1px rgba(250,100,0,0.06), 0 32px 80px rgba(0,0,0,0.65)" : "0 32px 80px rgba(0,0,0,0.12)",
107
- backdropFilter: "blur(20px)",
108
- WebkitBackdropFilter: "blur(20px)",
109
- animation: "dlsCardIn 0.35s cubic-bezier(0.34,1.2,0.64,1) both",
110
- overflow: "visible"
111
- }, children: [
112
- /* @__PURE__ */ jsx("div", { "aria-hidden": true, style: {
113
- position: "absolute",
114
- width: 500,
115
- height: 360,
116
- left: "50%",
117
- top: "50%",
118
- transform: "translateX(-50%) translateY(-50%)",
119
- background: `radial-gradient(ellipse at center, ${isDark ? "rgba(250,100,0,0.08)" : "rgba(250,100,0,0.05)"} 0%, transparent 70%)`,
120
- animation: "dlsBreath 3.2s ease-in-out infinite",
121
- pointerEvents: "none",
122
- borderRadius: 20
123
- } }),
124
- /* @__PURE__ */ jsx("span", { style: {
125
- fontFamily: "Inter,system-ui,sans-serif",
126
- fontSize: 11,
127
- fontWeight: 700,
128
- letterSpacing: "0.18em",
129
- textTransform: "uppercase",
130
- marginBottom: 6,
131
- color: isDark ? "rgba(250,100,0,0.55)" : "rgba(180,70,0,0.55)",
132
- animation: "dlsFadeUp 0.5s ease both"
133
- }, children: "FleetPanda" }),
134
- /* @__PURE__ */ jsx("div", { style: { display: "flex", lineHeight: 1, marginBottom: 6 }, children: ["D", "O", "J", "O"].map((letter, i) => /* @__PURE__ */ jsx("span", { style: {
135
- fontFamily: FONT_IMPACT,
136
- fontWeight: 900,
137
- fontSize: 110,
138
- color: "#FA6400",
139
- letterSpacing: "0.04em",
140
- display: "inline-block",
141
- transform: "skewX(-6deg)",
142
- textShadow,
143
- WebkitTextStroke: "0.5px #C24E00",
144
- animation: `dlsLetterDrop 0.6s cubic-bezier(0.34,1.45,0.64,1) ${i * 0.1}s both`
145
- }, children: letter }, i)) }),
146
- /* @__PURE__ */ jsx("span", { style: {
147
- fontFamily: "Inter,system-ui,sans-serif",
148
- fontSize: 13,
149
- fontWeight: 400,
150
- letterSpacing: "0.06em",
151
- color: subtitleColor,
152
- marginBottom: 32,
153
- animation: "dlsFadeUp 0.4s ease 0.55s both"
154
- }, children: "Developer Portal" }),
155
- /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center", gap: 0, marginBottom: 20, animation: "dlsFadeUp 0.4s ease 0.7s both" }, children: STAGES.map((stage, i) => {
156
- const isActive = i === activeStage;
157
- const isComplete = i < activeStage;
158
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 0 }, children: [
159
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 5, width: 64 }, children: [
160
- /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: 12, height: 12 }, children: [
161
- isActive && /* @__PURE__ */ jsx("div", { style: {
162
- position: "absolute",
163
- inset: -6,
164
- borderRadius: "50%",
165
- border: "1.5px solid rgba(250,100,0,0.5)",
166
- animation: "dlsPulse 1s ease-out infinite"
167
- } }),
168
- /* @__PURE__ */ jsx("div", { style: {
169
- width: 12,
170
- height: 12,
171
- borderRadius: "50%",
172
- background: isActive || isComplete ? "#FA6400" : isDark ? "#27272a" : "#e4e4e7",
173
- border: `2px solid ${isActive || isComplete ? "#C24E00" : isDark ? "#3f3f46" : "#d4d4d8"}`,
174
- boxShadow: isActive ? "0 0 14px rgba(250,100,0,0.7)" : "none",
175
- transition: "background 0.3s, box-shadow 0.3s"
176
- } })
177
- ] }),
178
- /* @__PURE__ */ jsx("span", { style: {
179
- fontFamily: "Inter,system-ui,sans-serif",
180
- fontSize: 8,
181
- fontWeight: 700,
182
- letterSpacing: "0.12em",
183
- textTransform: "uppercase",
184
- color: isActive ? "#FA6400" : isComplete ? mutedColor : mutedColor,
185
- transition: "color 0.3s"
186
- }, children: stage })
187
- ] }),
188
- i < STAGES.length - 1 && /* @__PURE__ */ jsx("div", { style: {
189
- width: 32,
190
- height: 2,
191
- marginBottom: 14,
192
- position: "relative",
193
- overflow: "hidden",
194
- background: isDark ? "#27272a" : "#e4e4e7",
195
- borderRadius: 2
196
- }, children: /* @__PURE__ */ jsx("div", { style: {
197
- position: "absolute",
198
- inset: 0,
199
- borderRadius: 2,
200
- background: "#FA6400",
201
- transformOrigin: "left center",
202
- transform: `scaleX(${i < activeStage ? 1 : isActive ? 0.5 : 0})`,
203
- transition: "transform 0.4s ease"
204
- } }) })
205
- ] }, stage);
206
- }) }),
207
- /* @__PURE__ */ jsx("span", { style: {
208
- fontFamily: FONT_MONO,
209
- fontSize: 11,
210
- color: mutedColor,
211
- letterSpacing: "0.04em",
212
- animation: "dlsFadeUp 0.3s ease both"
213
- }, children: LOADING_MESSAGES[msgIdx] }, msgKey)
214
- ] }),
215
- /* @__PURE__ */ jsx("style", { children: `
184
+ borderRadius: 2,
185
+ background: "#FA6400",
186
+ transformOrigin: "left center",
187
+ transform: `scaleX(${i < activeStage ? 1 : isActive ? 0.5 : 0})`,
188
+ transition: "transform 0.4s ease"
189
+ } })));
190
+ })), /* @__PURE__ */ React.createElement("span", { key: msgKey, style: {
191
+ fontFamily: FONT_MONO,
192
+ fontSize: 11,
193
+ color: mutedColor,
194
+ letterSpacing: "0.04em",
195
+ animation: "dlsFadeUp 0.3s ease both"
196
+ } }, LOADING_MESSAGES[msgIdx])), /* @__PURE__ */ React.createElement("style", null, `
216
197
  @keyframes dlsLetterDrop {
217
198
  from { opacity: 0; transform: skewX(-6deg) translateY(-32px) scale(1.1); }
218
199
  65% { transform: skewX(-6deg) translateY(6px) scale(0.95); }
@@ -234,8 +215,7 @@ function DojoLoadingOverlay() {
234
215
  from { opacity: 0; transform: translateX(-50%) translateY(-46%); }
235
216
  to { opacity: 1; transform: translateX(-50%) translateY(-50%); }
236
217
  }
237
- ` })
238
- ] });
218
+ `));
239
219
  }
240
220
  function CompactSparkline({
241
221
  values,
@@ -265,49 +245,34 @@ function CompactSparkline({
265
245
  const barW = (VW - gap * (values.length - 1)) / values.length;
266
246
  const hoverX = hoverIdx !== null ? hoverIdx * (barW + gap) + barW / 2 : null;
267
247
  const tooltipX = hoverX !== null ? Math.min(hoverX - 34, VW - 72) : 0;
268
- return /* @__PURE__ */ jsxs("div", { children: [
269
- /* @__PURE__ */ jsxs(
270
- "svg",
271
- {
272
- viewBox: `0 0 ${VW} ${height}`,
273
- width: "100%",
274
- style: { display: "block", overflow: "visible" },
275
- onMouseMove: handleMouseMove,
276
- onMouseLeave: () => setHoverIdx(null),
277
- children: [
278
- values.map((v, i) => {
279
- const h = Math.max(2, v / max2 * (height - 4));
280
- return /* @__PURE__ */ jsx(
281
- "rect",
282
- {
283
- x: i * (barW + gap),
284
- y: height - h,
285
- width: barW,
286
- height: h,
287
- rx: 1.5,
288
- fill: color,
289
- opacity: i === hoverIdx ? 1 : 0.3 + i / values.length * 0.55
290
- },
291
- i
292
- );
293
- }),
294
- /* @__PURE__ */ jsx("line", { x1: 0, y1: height - 0.5, x2: VW, y2: height - 0.5, stroke: mutedBorder, strokeWidth: 1 }),
295
- hoverIdx !== null && hoverX !== null && /* @__PURE__ */ jsxs(Fragment, { children: [
296
- /* @__PURE__ */ jsx("rect", { x: tooltipX, y: 2, width: 72, height: 22, rx: 4, fill: tooltipBg, stroke: tooltipBdr, strokeWidth: 1 }),
297
- /* @__PURE__ */ jsxs("text", { x: tooltipX + 36, y: 17, textAnchor: "middle", fill: tooltipText, fontSize: 9, fontWeight: 600, fontFamily: font, children: [
298
- weekLabels[hoverIdx],
299
- ": ",
300
- formatValue(values[hoverIdx])
301
- ] })
302
- ] })
303
- ]
304
- }
305
- ),
306
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", marginTop: 3 }, children: [
307
- /* @__PURE__ */ jsx("span", { style: { fontSize: 9, color: isDark ? "#3f3f46" : "#a1a1aa" }, children: weekLabels[0] }),
308
- /* @__PURE__ */ jsx("span", { style: { fontSize: 9, color: isDark ? "#3f3f46" : "#a1a1aa" }, children: weekLabels[weekLabels.length - 1] })
309
- ] })
310
- ] });
248
+ return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
249
+ "svg",
250
+ {
251
+ viewBox: `0 0 ${VW} ${height}`,
252
+ width: "100%",
253
+ style: { display: "block", overflow: "visible" },
254
+ onMouseMove: handleMouseMove,
255
+ onMouseLeave: () => setHoverIdx(null)
256
+ },
257
+ values.map((v, i) => {
258
+ const h = Math.max(2, v / max2 * (height - 4));
259
+ return /* @__PURE__ */ React.createElement(
260
+ "rect",
261
+ {
262
+ key: i,
263
+ x: i * (barW + gap),
264
+ y: height - h,
265
+ width: barW,
266
+ height: h,
267
+ rx: 1.5,
268
+ fill: color,
269
+ opacity: i === hoverIdx ? 1 : 0.3 + i / values.length * 0.55
270
+ }
271
+ );
272
+ }),
273
+ /* @__PURE__ */ React.createElement("line", { x1: 0, y1: height - 0.5, x2: VW, y2: height - 0.5, stroke: mutedBorder, strokeWidth: 1 }),
274
+ hoverIdx !== null && hoverX !== null && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("rect", { x: tooltipX, y: 2, width: 72, height: 22, rx: 4, fill: tooltipBg, stroke: tooltipBdr, strokeWidth: 1 }), /* @__PURE__ */ React.createElement("text", { x: tooltipX + 36, y: 17, textAnchor: "middle", fill: tooltipText, fontSize: 9, fontWeight: 600, fontFamily: font }, weekLabels[hoverIdx], ": ", formatValue(values[hoverIdx])))
275
+ ), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "space-between", marginTop: 3 } }, /* @__PURE__ */ React.createElement("span", { style: { fontSize: 9, color: isDark ? "#3f3f46" : "#a1a1aa" } }, weekLabels[0]), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 9, color: isDark ? "#3f3f46" : "#a1a1aa" } }, weekLabels[weekLabels.length - 1])));
311
276
  }
312
277
  if (values.length < 2) return null;
313
278
  const max = Math.max(...values, 0.01);
@@ -322,37 +287,21 @@ function CompactSparkline({
322
287
  const area = `${d} L${pts[pts.length - 1][0]},${height} L${pts[0][0]},${height} Z`;
323
288
  const hoverPt = hoverIdx !== null ? pts[hoverIdx] : null;
324
289
  const tooltipX2 = hoverPt ? Math.min(hoverPt[0] - 34, VW - 72) : 0;
325
- return /* @__PURE__ */ jsxs("div", { children: [
326
- /* @__PURE__ */ jsxs(
327
- "svg",
328
- {
329
- viewBox: `0 0 ${VW} ${height}`,
330
- width: "100%",
331
- style: { display: "block", overflow: "visible" },
332
- onMouseMove: handleMouseMove,
333
- onMouseLeave: () => setHoverIdx(null),
334
- children: [
335
- /* @__PURE__ */ jsx("path", { d: area, fill: color, opacity: 0.07 }),
336
- /* @__PURE__ */ jsx("path", { d, fill: "none", stroke: color, strokeWidth: 1.5, strokeLinejoin: "round", strokeLinecap: "round" }),
337
- pts.map(([x, y], i) => /* @__PURE__ */ jsx("circle", { cx: x, cy: y, r: i === hoverIdx ? 3.5 : i === pts.length - 1 ? 2.5 : 0, fill: color }, i)),
338
- /* @__PURE__ */ jsx("line", { x1: 0, y1: height - 0.5, x2: VW, y2: height - 0.5, stroke: mutedBorder, strokeWidth: 1 }),
339
- hoverPt && hoverIdx !== null && /* @__PURE__ */ jsxs(Fragment, { children: [
340
- /* @__PURE__ */ jsx("line", { x1: hoverPt[0], y1: 0, x2: hoverPt[0], y2: height, stroke: color, strokeWidth: 1, strokeDasharray: "2 2", opacity: 0.4 }),
341
- /* @__PURE__ */ jsx("rect", { x: tooltipX2, y: 2, width: 72, height: 22, rx: 4, fill: tooltipBg, stroke: tooltipBdr, strokeWidth: 1 }),
342
- /* @__PURE__ */ jsxs("text", { x: tooltipX2 + 36, y: 17, textAnchor: "middle", fill: tooltipText, fontSize: 9, fontWeight: 600, fontFamily: font, children: [
343
- weekLabels[hoverIdx],
344
- ": ",
345
- formatValue(values[hoverIdx])
346
- ] })
347
- ] })
348
- ]
349
- }
350
- ),
351
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", marginTop: 3 }, children: [
352
- /* @__PURE__ */ jsx("span", { style: { fontSize: 9, color: isDark ? "#3f3f46" : "#a1a1aa" }, children: weekLabels[0] }),
353
- /* @__PURE__ */ jsx("span", { style: { fontSize: 9, color: isDark ? "#3f3f46" : "#a1a1aa" }, children: weekLabels[weekLabels.length - 1] })
354
- ] })
355
- ] });
290
+ return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
291
+ "svg",
292
+ {
293
+ viewBox: `0 0 ${VW} ${height}`,
294
+ width: "100%",
295
+ style: { display: "block", overflow: "visible" },
296
+ onMouseMove: handleMouseMove,
297
+ onMouseLeave: () => setHoverIdx(null)
298
+ },
299
+ /* @__PURE__ */ React.createElement("path", { d: area, fill: color, opacity: 0.07 }),
300
+ /* @__PURE__ */ React.createElement("path", { d, fill: "none", stroke: color, strokeWidth: 1.5, strokeLinejoin: "round", strokeLinecap: "round" }),
301
+ pts.map(([x, y], i) => /* @__PURE__ */ React.createElement("circle", { key: i, cx: x, cy: y, r: i === hoverIdx ? 3.5 : i === pts.length - 1 ? 2.5 : 0, fill: color })),
302
+ /* @__PURE__ */ React.createElement("line", { x1: 0, y1: height - 0.5, x2: VW, y2: height - 0.5, stroke: mutedBorder, strokeWidth: 1 }),
303
+ hoverPt && hoverIdx !== null && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("line", { x1: hoverPt[0], y1: 0, x2: hoverPt[0], y2: height, stroke: color, strokeWidth: 1, strokeDasharray: "2 2", opacity: 0.4 }), /* @__PURE__ */ React.createElement("rect", { x: tooltipX2, y: 2, width: 72, height: 22, rx: 4, fill: tooltipBg, stroke: tooltipBdr, strokeWidth: 1 }), /* @__PURE__ */ React.createElement("text", { x: tooltipX2 + 36, y: 17, textAnchor: "middle", fill: tooltipText, fontSize: 9, fontWeight: 600, fontFamily: font }, weekLabels[hoverIdx], ": ", formatValue(values[hoverIdx])))
304
+ ), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "space-between", marginTop: 3 } }, /* @__PURE__ */ React.createElement("span", { style: { fontSize: 9, color: isDark ? "#3f3f46" : "#a1a1aa" } }, weekLabels[0]), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 9, color: isDark ? "#3f3f46" : "#a1a1aa" } }, weekLabels[weekLabels.length - 1])));
356
305
  }
357
306
  function DetailedChart({
358
307
  values,
@@ -416,194 +365,168 @@ function DetailedChart({
416
365
  const y = cRect ? e.clientY - cRect.top : 0;
417
366
  setTooltip({ x, y, primary: formatValue(values[idx]), secondary: weekLabels[idx] });
418
367
  };
419
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, style: { position: "relative" }, children: [
420
- /* @__PURE__ */ jsxs(
421
- "svg",
368
+ return /* @__PURE__ */ React.createElement("div", { ref: containerRef, style: { position: "relative" } }, /* @__PURE__ */ React.createElement(
369
+ "svg",
370
+ {
371
+ viewBox: `0 0 ${W} ${H}`,
372
+ width: "100%",
373
+ style: { display: "block" },
374
+ onMouseMove: handleSvgMouseMove,
375
+ onMouseLeave: () => {
376
+ setHoverIdx(null);
377
+ setTooltip(null);
378
+ }
379
+ },
380
+ /* @__PURE__ */ React.createElement("defs", null, /* @__PURE__ */ React.createElement("linearGradient", { id: gradId, x1: "0", y1: "0", x2: "0", y2: "1" }, /* @__PURE__ */ React.createElement("stop", { offset: "0%", stopColor: color, stopOpacity: 0.28 }), /* @__PURE__ */ React.createElement("stop", { offset: "100%", stopColor: color, stopOpacity: 0 })), /* @__PURE__ */ React.createElement("clipPath", { id: `prClip_${gradId}` }, /* @__PURE__ */ React.createElement("rect", { x: PAD.left, y: PAD.top - 8, width: cW, height: cH + 16 }))),
381
+ yTicks.map((v, i) => {
382
+ const y = PAD.top + yScale(v);
383
+ return /* @__PURE__ */ React.createElement("g", { key: i }, i > 0 && /* @__PURE__ */ React.createElement("line", { x1: PAD.left, y1: y, x2: PAD.left + cW, y2: y, stroke: gridColor, strokeWidth: 1, strokeDasharray: "3 4" }), /* @__PURE__ */ React.createElement("text", { x: PAD.left - 6, y: y + 4, textAnchor: "end", fill: labelColor, fontSize: 10, fontFamily: font }, formatValue(v)));
384
+ }),
385
+ /* @__PURE__ */ React.createElement("line", { x1: PAD.left, y1: PAD.top + cH, x2: PAD.left + cW, y2: PAD.top + cH, stroke: axisColor, strokeWidth: 1 }),
386
+ type === "bar" ? values.map((v, i) => {
387
+ const bH = Math.max(2, (v - dataMin) / dataRange * cH);
388
+ const x = PAD.left + i * (barW + gap);
389
+ return /* @__PURE__ */ React.createElement(
390
+ "rect",
391
+ {
392
+ key: i,
393
+ x,
394
+ y: PAD.top + cH - bH,
395
+ width: barW,
396
+ height: bH,
397
+ rx: 3,
398
+ fill: color,
399
+ opacity: i === hoverIdx ? 0.95 : 0.4 + i / Math.max(values.length - 1, 1) * 0.45
400
+ }
401
+ );
402
+ }) : /* @__PURE__ */ React.createElement(React.Fragment, null, areaPath && /* @__PURE__ */ React.createElement("path", { d: areaPath, fill: `url(#${gradId})` }), /* @__PURE__ */ React.createElement("path", { d: linePath, fill: "none", stroke: color, strokeWidth: 2.5, strokeLinejoin: "round", strokeLinecap: "round" }), pts.map(([x, y], i) => /* @__PURE__ */ React.createElement(
403
+ "circle",
422
404
  {
423
- viewBox: `0 0 ${W} ${H}`,
424
- width: "100%",
425
- style: { display: "block" },
426
- onMouseMove: handleSvgMouseMove,
427
- onMouseLeave: () => {
428
- setHoverIdx(null);
429
- setTooltip(null);
405
+ key: i,
406
+ cx: x,
407
+ cy: y,
408
+ r: i === hoverIdx ? 5.5 : 4,
409
+ fill: isDark ? "#0f0f12" : "#ffffff",
410
+ stroke: color,
411
+ strokeWidth: i === hoverIdx ? 2.5 : 1.5
412
+ }
413
+ ))),
414
+ prs && type === "line" && /* @__PURE__ */ React.createElement("g", { clipPath: `url(#prClip_${gradId})` }, prs.map((pr, dotIdx) => {
415
+ const mergedMs = new Date(pr.mergedAt).getTime();
416
+ const bIdx = nearestBucket(mergedMs);
417
+ const jitter = ((pr.number * 17 + dotIdx * 11) % 13 - 6) * 5;
418
+ const px = PAD.left + xIdx(bIdx) + jitter;
419
+ const rawPy = PAD.top + yScale(pr.durationHours);
420
+ const py = Math.max(PAD.top + 4, Math.min(PAD.top + cH - 4, rawPy));
421
+ const isHov = pr.number === hoveredPrNumber;
422
+ const trendPt = pts[bIdx];
423
+ return /* @__PURE__ */ React.createElement(
424
+ "g",
425
+ {
426
+ key: pr.number,
427
+ style: { cursor: "pointer" },
428
+ onMouseEnter: (e) => {
429
+ e.stopPropagation();
430
+ const cRect = containerRef.current?.getBoundingClientRect();
431
+ const x = cRect ? e.clientX - cRect.left : 0;
432
+ const y = cRect ? e.clientY - cRect.top : 0;
433
+ setTooltip({
434
+ x,
435
+ y,
436
+ primary: `#${pr.number}: ${pr.title}`,
437
+ secondary: `${formatValue(pr.durationHours)} \xB7 ${new Date(pr.mergedAt).toLocaleDateString("en-US", { month: "short", day: "numeric" })}`
438
+ });
439
+ onPrHover?.(pr.number);
440
+ },
441
+ onMouseLeave: () => {
442
+ setTooltip(null);
443
+ onPrHover?.(null);
444
+ }
430
445
  },
431
- children: [
432
- /* @__PURE__ */ jsxs("defs", { children: [
433
- /* @__PURE__ */ jsxs("linearGradient", { id: gradId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
434
- /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: color, stopOpacity: 0.28 }),
435
- /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: color, stopOpacity: 0 })
436
- ] }),
437
- /* @__PURE__ */ jsx("clipPath", { id: `prClip_${gradId}`, children: /* @__PURE__ */ jsx("rect", { x: PAD.left, y: PAD.top - 8, width: cW, height: cH + 16 }) })
438
- ] }),
439
- yTicks.map((v, i) => {
440
- const y = PAD.top + yScale(v);
441
- return /* @__PURE__ */ jsxs("g", { children: [
442
- i > 0 && /* @__PURE__ */ jsx("line", { x1: PAD.left, y1: y, x2: PAD.left + cW, y2: y, stroke: gridColor, strokeWidth: 1, strokeDasharray: "3 4" }),
443
- /* @__PURE__ */ jsx("text", { x: PAD.left - 6, y: y + 4, textAnchor: "end", fill: labelColor, fontSize: 10, fontFamily: font, children: formatValue(v) })
444
- ] }, i);
445
- }),
446
- /* @__PURE__ */ jsx("line", { x1: PAD.left, y1: PAD.top + cH, x2: PAD.left + cW, y2: PAD.top + cH, stroke: axisColor, strokeWidth: 1 }),
447
- type === "bar" ? values.map((v, i) => {
448
- const bH = Math.max(2, (v - dataMin) / dataRange * cH);
449
- const x = PAD.left + i * (barW + gap);
450
- return /* @__PURE__ */ jsx(
451
- "rect",
452
- {
453
- x,
454
- y: PAD.top + cH - bH,
455
- width: barW,
456
- height: bH,
457
- rx: 3,
458
- fill: color,
459
- opacity: i === hoverIdx ? 0.95 : 0.4 + i / Math.max(values.length - 1, 1) * 0.45
460
- },
461
- i
462
- );
463
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [
464
- areaPath && /* @__PURE__ */ jsx("path", { d: areaPath, fill: `url(#${gradId})` }),
465
- /* @__PURE__ */ jsx("path", { d: linePath, fill: "none", stroke: color, strokeWidth: 2.5, strokeLinejoin: "round", strokeLinecap: "round" }),
466
- pts.map(([x, y], i) => /* @__PURE__ */ jsx(
467
- "circle",
468
- {
469
- cx: x,
470
- cy: y,
471
- r: i === hoverIdx ? 5.5 : 4,
472
- fill: isDark ? "#0f0f12" : "#ffffff",
473
- stroke: color,
474
- strokeWidth: i === hoverIdx ? 2.5 : 1.5
475
- },
476
- i
477
- ))
478
- ] }),
479
- prs && type === "line" && /* @__PURE__ */ jsx("g", { clipPath: `url(#prClip_${gradId})`, children: prs.map((pr, dotIdx) => {
480
- const mergedMs = new Date(pr.mergedAt).getTime();
481
- const bIdx = nearestBucket(mergedMs);
482
- const jitter = ((pr.number * 17 + dotIdx * 11) % 13 - 6) * 5;
483
- const px = PAD.left + xIdx(bIdx) + jitter;
484
- const rawPy = PAD.top + yScale(pr.durationHours);
485
- const py = Math.max(PAD.top + 4, Math.min(PAD.top + cH - 4, rawPy));
486
- const isHov = pr.number === hoveredPrNumber;
487
- const trendPt = pts[bIdx];
488
- return /* @__PURE__ */ jsxs(
489
- "g",
490
- {
491
- style: { cursor: "pointer" },
492
- onMouseEnter: (e) => {
493
- e.stopPropagation();
494
- const cRect = containerRef.current?.getBoundingClientRect();
495
- const x = cRect ? e.clientX - cRect.left : 0;
496
- const y = cRect ? e.clientY - cRect.top : 0;
497
- setTooltip({
498
- x,
499
- y,
500
- primary: `#${pr.number}: ${pr.title}`,
501
- secondary: `${formatValue(pr.durationHours)} \xB7 ${new Date(pr.mergedAt).toLocaleDateString("en-US", { month: "short", day: "numeric" })}`
502
- });
503
- onPrHover?.(pr.number);
504
- },
505
- onMouseLeave: () => {
506
- setTooltip(null);
507
- onPrHover?.(null);
508
- },
509
- children: [
510
- trendPt && /* @__PURE__ */ jsx(
511
- "line",
512
- {
513
- x1: px,
514
- y1: py,
515
- x2: trendPt[0],
516
- y2: trendPt[1],
517
- stroke: color,
518
- strokeWidth: isHov ? 1 : 0.5,
519
- strokeDasharray: "2 3",
520
- opacity: isHov ? 0.5 : 0.2
521
- }
522
- ),
523
- /* @__PURE__ */ jsx("circle", { cx: px, cy: py, r: isHov ? 11 : 7, fill: color, opacity: isHov ? 0.2 : 0.1 }),
524
- /* @__PURE__ */ jsx(
525
- "circle",
526
- {
527
- cx: px,
528
- cy: py,
529
- r: isHov ? 5.5 : 3.5,
530
- fill: isHov ? color : isDark ? "#1a1a20" : "#fff",
531
- stroke: color,
532
- strokeWidth: isHov ? 0 : 1.5,
533
- opacity: isHov ? 1 : 0.8
534
- }
535
- )
536
- ]
537
- },
538
- pr.number
539
- );
540
- }) }),
541
- hoverIdx !== null && type === "line" && pts[hoverIdx] && /* @__PURE__ */ jsxs(Fragment, { children: [
542
- /* @__PURE__ */ jsx(
543
- "line",
544
- {
545
- x1: pts[hoverIdx][0],
546
- y1: PAD.top,
547
- x2: pts[hoverIdx][0],
548
- y2: PAD.top + cH,
549
- stroke: color,
550
- strokeWidth: 1,
551
- strokeDasharray: "3 3",
552
- opacity: 0.3
553
- }
554
- ),
555
- /* @__PURE__ */ jsx(
556
- "line",
557
- {
558
- x1: PAD.left,
559
- y1: pts[hoverIdx][1],
560
- x2: PAD.left + cW,
561
- y2: pts[hoverIdx][1],
562
- stroke: color,
563
- strokeWidth: 1,
564
- strokeDasharray: "3 3",
565
- opacity: 0.2
566
- }
567
- )
568
- ] }),
569
- weekLabels.map((lbl, i) => {
570
- const total = weekLabels.length;
571
- const step = Math.max(1, Math.floor(total / 5));
572
- if (i !== 0 && i !== total - 1 && i % step !== 0) return null;
573
- const x = type === "bar" ? PAD.left + i * (barW + gap) + barW / 2 : PAD.left + xIdx(i);
574
- return /* @__PURE__ */ jsx("text", { x, y: H - 8, textAnchor: "middle", fill: labelColor, fontSize: 10, fontFamily: font, children: lbl }, i);
575
- })
576
- ]
446
+ trendPt && /* @__PURE__ */ React.createElement(
447
+ "line",
448
+ {
449
+ x1: px,
450
+ y1: py,
451
+ x2: trendPt[0],
452
+ y2: trendPt[1],
453
+ stroke: color,
454
+ strokeWidth: isHov ? 1 : 0.5,
455
+ strokeDasharray: "2 3",
456
+ opacity: isHov ? 0.5 : 0.2
457
+ }
458
+ ),
459
+ /* @__PURE__ */ React.createElement("circle", { cx: px, cy: py, r: isHov ? 11 : 7, fill: color, opacity: isHov ? 0.2 : 0.1 }),
460
+ /* @__PURE__ */ React.createElement(
461
+ "circle",
462
+ {
463
+ cx: px,
464
+ cy: py,
465
+ r: isHov ? 5.5 : 3.5,
466
+ fill: isHov ? color : isDark ? "#1a1a20" : "#fff",
467
+ stroke: color,
468
+ strokeWidth: isHov ? 0 : 1.5,
469
+ opacity: isHov ? 1 : 0.8
470
+ }
471
+ )
472
+ );
473
+ })),
474
+ hoverIdx !== null && type === "line" && pts[hoverIdx] && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
475
+ "line",
476
+ {
477
+ x1: pts[hoverIdx][0],
478
+ y1: PAD.top,
479
+ x2: pts[hoverIdx][0],
480
+ y2: PAD.top + cH,
481
+ stroke: color,
482
+ strokeWidth: 1,
483
+ strokeDasharray: "3 3",
484
+ opacity: 0.3
485
+ }
486
+ ), /* @__PURE__ */ React.createElement(
487
+ "line",
488
+ {
489
+ x1: PAD.left,
490
+ y1: pts[hoverIdx][1],
491
+ x2: PAD.left + cW,
492
+ y2: pts[hoverIdx][1],
493
+ stroke: color,
494
+ strokeWidth: 1,
495
+ strokeDasharray: "3 3",
496
+ opacity: 0.2
577
497
  }
578
- ),
579
- tooltip && (() => {
580
- const TW = 220;
581
- const cW2 = containerRef.current?.offsetWidth ?? 800;
582
- const nearRight = tooltip.x + TW + 12 > cW2;
583
- return /* @__PURE__ */ jsxs("div", { style: {
584
- position: "absolute",
585
- left: nearRight ? tooltip.x - TW - 8 : tooltip.x + 8,
586
- top: tooltip.y - 36,
587
- zIndex: 9999,
588
- pointerEvents: "none",
589
- background: isDark ? "#18181b" : "#ffffff",
590
- border: `1px solid ${isDark ? "#3f3f46" : "#e4e4e7"}`,
591
- borderRadius: 7,
592
- padding: "7px 10px",
593
- boxShadow: isDark ? "0 6px 20px rgba(0,0,0,0.55)" : "0 6px 20px rgba(0,0,0,0.10)",
594
- width: TW
595
- }, children: [
596
- /* @__PURE__ */ jsx("div", { style: {
597
- fontSize: 12,
598
- fontWeight: 600,
599
- color: isDark ? "#ededef" : "#111114",
600
- fontFamily: font,
601
- lineHeight: 1.4
602
- }, children: tooltip.primary }),
603
- tooltip.secondary && /* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: isDark ? "#71717a" : "#a1a1aa", fontFamily: font, marginTop: 2 }, children: tooltip.secondary })
604
- ] });
605
- })()
606
- ] });
498
+ )),
499
+ weekLabels.map((lbl, i) => {
500
+ const total = weekLabels.length;
501
+ const step = Math.max(1, Math.floor(total / 5));
502
+ if (i !== 0 && i !== total - 1 && i % step !== 0) return null;
503
+ const x = type === "bar" ? PAD.left + i * (barW + gap) + barW / 2 : PAD.left + xIdx(i);
504
+ return /* @__PURE__ */ React.createElement("text", { key: i, x, y: H - 8, textAnchor: "middle", fill: labelColor, fontSize: 10, fontFamily: font }, lbl);
505
+ })
506
+ ), tooltip && (() => {
507
+ const TW = 220;
508
+ const cW2 = containerRef.current?.offsetWidth ?? 800;
509
+ const nearRight = tooltip.x + TW + 12 > cW2;
510
+ return /* @__PURE__ */ React.createElement("div", { style: {
511
+ position: "absolute",
512
+ left: nearRight ? tooltip.x - TW - 8 : tooltip.x + 8,
513
+ top: tooltip.y - 36,
514
+ zIndex: 9999,
515
+ pointerEvents: "none",
516
+ background: isDark ? "#18181b" : "#ffffff",
517
+ border: `1px solid ${isDark ? "#3f3f46" : "#e4e4e7"}`,
518
+ borderRadius: 7,
519
+ padding: "7px 10px",
520
+ boxShadow: isDark ? "0 6px 20px rgba(0,0,0,0.55)" : "0 6px 20px rgba(0,0,0,0.10)",
521
+ width: TW
522
+ } }, /* @__PURE__ */ React.createElement("div", { style: {
523
+ fontSize: 12,
524
+ fontWeight: 600,
525
+ color: isDark ? "#ededef" : "#111114",
526
+ fontFamily: font,
527
+ lineHeight: 1.4
528
+ } }, tooltip.primary), tooltip.secondary && /* @__PURE__ */ React.createElement("div", { style: { fontSize: 11, color: isDark ? "#71717a" : "#a1a1aa", fontFamily: font, marginTop: 2 } }, tooltip.secondary));
529
+ })());
607
530
  }
608
531
  function DetailedOverlay({
609
532
  data,
@@ -627,7 +550,7 @@ function DetailedOverlay({
627
550
  if (el) el.scrollIntoView({ behavior: "smooth", block: "nearest" });
628
551
  }
629
552
  }, [hoveredPrNumber]);
630
- const { title, metric, description, lowerIsBetter, sparklineValues, sparklineType, sparklineColor, weekLabels, bucketMidMs, prs } = data;
553
+ const { title, metric, description, lowerIsBetter, sparklineValues, sparklineType, sparklineColor, weekLabels, bucketMidMs, prs, prListLabel } = data;
631
554
  const isDeployFreq = sparklineType === "bar";
632
555
  const displayValue = metric.unit === "hours" ? formatDuration(metric.value) : String(metric.value);
633
556
  const displayUnit = metric.unit === "hours" ? "" : metric.unit;
@@ -644,323 +567,234 @@ function DetailedOverlay({
644
567
  const sortedDates = Object.keys(prsByDate).sort(
645
568
  (a, b) => new Date(prsByDate[b][0].mergedAt).getTime() - new Date(prsByDate[a][0].mergedAt).getTime()
646
569
  );
647
- return /* @__PURE__ */ jsxs(Fragment, { children: [
648
- /* @__PURE__ */ jsx("div", { onClick: onClose, style: {
649
- position: "fixed",
650
- inset: 0,
651
- zIndex: 499,
652
- background: isDark ? "rgba(0,0,0,0.6)" : "rgba(0,0,0,0.3)",
653
- backdropFilter: "blur(4px)"
654
- } }),
655
- /* @__PURE__ */ jsxs("div", { style: {
656
- position: "fixed",
657
- top: "50%",
658
- left: "50%",
659
- transform: "translateX(-50%) translateY(-50%)",
660
- width: "min(92vw, 960px)",
661
- maxHeight: "calc(100vh - 80px)",
662
- zIndex: 500,
663
- background: isDark ? "#09090c" : "#fafafa",
664
- border: `1px solid ${borderColor}`,
665
- borderRadius: 14,
666
- display: "flex",
667
- flexDirection: "column",
668
- overflow: "hidden",
669
- animation: "detailExpandIn 0.2s cubic-bezier(0.34,1.2,0.64,1)",
670
- boxShadow: isDark ? "0 24px 64px rgba(0,0,0,0.7)" : "0 24px 64px rgba(0,0,0,0.18)"
671
- }, children: [
672
- /* @__PURE__ */ jsxs("div", { style: {
570
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { onClick: onClose, style: {
571
+ position: "fixed",
572
+ inset: 0,
573
+ zIndex: 499,
574
+ background: isDark ? "rgba(0,0,0,0.6)" : "rgba(0,0,0,0.3)",
575
+ backdropFilter: "blur(4px)"
576
+ } }), /* @__PURE__ */ React.createElement("div", { style: {
577
+ position: "fixed",
578
+ top: "50%",
579
+ left: "50%",
580
+ transform: "translateX(-50%) translateY(-50%)",
581
+ width: "min(92vw, 960px)",
582
+ maxHeight: "calc(100vh - 80px)",
583
+ zIndex: 500,
584
+ background: isDark ? "#09090c" : "#fafafa",
585
+ border: `1px solid ${borderColor}`,
586
+ borderRadius: 14,
587
+ display: "flex",
588
+ flexDirection: "column",
589
+ overflow: "hidden",
590
+ animation: "detailExpandIn 0.2s cubic-bezier(0.34,1.2,0.64,1)",
591
+ boxShadow: isDark ? "0 24px 64px rgba(0,0,0,0.7)" : "0 24px 64px rgba(0,0,0,0.18)"
592
+ } }, /* @__PURE__ */ React.createElement("div", { style: {
593
+ display: "flex",
594
+ justifyContent: "space-between",
595
+ alignItems: "flex-start",
596
+ padding: "20px 24px 16px",
597
+ borderBottom: `1px solid ${borderColor}`,
598
+ flexShrink: 0
599
+ } }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { style: { fontSize: 10, fontWeight: 700, color: mutedColor, textTransform: "uppercase", letterSpacing: "0.12em", marginBottom: 8 } }, title, " \xB7 ", weekLabels[0], " \u2013 ", weekLabels[weekLabels.length - 1]), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "baseline", gap: 8, flexWrap: "wrap" } }, /* @__PURE__ */ React.createElement("span", { style: { fontSize: 52, fontWeight: 700, color: theme.palette.text.primary, lineHeight: 1, letterSpacing: "-0.02em" } }, displayValue), displayUnit && /* @__PURE__ */ React.createElement("span", { style: { fontSize: 18, color: mutedColor, fontWeight: 500 } }, displayUnit), /* @__PURE__ */ React.createElement(RatingBadge, { rating: metric.rating })), /* @__PURE__ */ React.createElement("div", { style: { fontSize: 12, color: mutedColor, marginTop: 6 } }, description, " \xB7 ", /* @__PURE__ */ React.createElement("span", { style: { fontStyle: "italic" } }, targetLabel))), /* @__PURE__ */ React.createElement(
600
+ "button",
601
+ {
602
+ type: "button",
603
+ onClick: onClose,
604
+ style: {
605
+ all: "unset",
606
+ cursor: "pointer",
607
+ width: 30,
608
+ height: 30,
673
609
  display: "flex",
674
- justifyContent: "space-between",
675
- alignItems: "flex-start",
676
- padding: "20px 24px 16px",
677
- borderBottom: `1px solid ${borderColor}`,
610
+ alignItems: "center",
611
+ justifyContent: "center",
612
+ borderRadius: "50%",
613
+ background: isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.06)",
614
+ color: mutedColor,
615
+ fontSize: 18,
616
+ fontWeight: 300,
617
+ transition: "all 0.12s",
678
618
  flexShrink: 0
679
- }, children: [
680
- /* @__PURE__ */ jsxs("div", { children: [
681
- /* @__PURE__ */ jsxs("div", { style: { fontSize: 10, fontWeight: 700, color: mutedColor, textTransform: "uppercase", letterSpacing: "0.12em", marginBottom: 8 }, children: [
682
- title,
683
- " \xB7 ",
684
- weekLabels[0],
685
- " \u2013 ",
686
- weekLabels[weekLabels.length - 1]
687
- ] }),
688
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "baseline", gap: 8, flexWrap: "wrap" }, children: [
689
- /* @__PURE__ */ jsx("span", { style: { fontSize: 52, fontWeight: 700, color: theme.palette.text.primary, lineHeight: 1, letterSpacing: "-0.02em" }, children: displayValue }),
690
- displayUnit && /* @__PURE__ */ jsx("span", { style: { fontSize: 18, color: mutedColor, fontWeight: 500 }, children: displayUnit }),
691
- /* @__PURE__ */ jsx(RatingBadge, { rating: metric.rating })
692
- ] }),
693
- /* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: mutedColor, marginTop: 6 }, children: [
694
- description,
695
- " \xB7 ",
696
- /* @__PURE__ */ jsx("span", { style: { fontStyle: "italic" }, children: targetLabel })
697
- ] })
698
- ] }),
699
- /* @__PURE__ */ jsx(
700
- "button",
701
- {
702
- type: "button",
703
- onClick: onClose,
704
- style: {
705
- all: "unset",
706
- cursor: "pointer",
707
- width: 30,
708
- height: 30,
709
- display: "flex",
710
- alignItems: "center",
711
- justifyContent: "center",
712
- borderRadius: "50%",
713
- background: isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.06)",
714
- color: mutedColor,
715
- fontSize: 18,
716
- fontWeight: 300,
717
- transition: "all 0.12s",
718
- flexShrink: 0
719
- },
720
- onMouseEnter: (e) => {
721
- e.currentTarget.style.background = isDark ? "rgba(255,255,255,0.12)" : "rgba(0,0,0,0.1)";
722
- },
723
- onMouseLeave: (e) => {
724
- e.currentTarget.style.background = isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.06)";
725
- },
726
- children: "\u2715"
727
- }
728
- )
729
- ] }),
730
- /* @__PURE__ */ jsxs("div", { style: { flex: 1, overflow: "auto", padding: "20px 24px", display: "flex", flexDirection: "column", gap: 20 }, children: [
731
- weekLabels.length >= 2 ? /* @__PURE__ */ jsx(
732
- DetailedChart,
733
- {
734
- values: sparklineValues,
735
- weekLabels,
736
- bucketMidMs,
737
- color: sparklineColor,
738
- type: sparklineType,
739
- formatValue: formatVal,
740
- prs: isDeployFreq ? void 0 : prs,
741
- days,
742
- hoveredPrNumber,
743
- onPrHover: setHoveredPrNumber
744
- }
745
- ) : /* @__PURE__ */ jsx("div", { style: { color: mutedColor, fontSize: 13, textAlign: "center", padding: "48px 0" }, children: "Not enough data \u2014 select a wider date range." }),
746
- isDeployFreq && prs && prs.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
747
- /* @__PURE__ */ jsxs("div", { style: {
748
- fontSize: 10,
749
- fontWeight: 700,
750
- color: mutedColor,
751
- textTransform: "uppercase",
752
- letterSpacing: "0.12em",
753
- marginBottom: 12,
754
- borderTop: `1px solid ${borderColor}`,
755
- paddingTop: 12
756
- }, children: [
757
- "All deployments \xB7 ",
758
- prs.length,
759
- " PRs merged"
760
- ] }),
761
- /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: sortedDates.map((date) => /* @__PURE__ */ jsxs("div", { children: [
762
- /* @__PURE__ */ jsxs("div", { style: {
763
- fontSize: 11,
764
- fontWeight: 700,
765
- color: sparklineColor,
766
- marginBottom: 6,
767
- display: "flex",
768
- alignItems: "center",
769
- gap: 8
770
- }, children: [
771
- /* @__PURE__ */ jsx("span", { children: date }),
772
- /* @__PURE__ */ jsxs("span", { style: { color: mutedColor, fontWeight: 500 }, children: [
773
- "\xB7 ",
774
- prsByDate[date].length,
775
- " PR",
776
- prsByDate[date].length > 1 ? "s" : ""
777
- ] })
778
- ] }),
779
- /* @__PURE__ */ jsx("div", { style: {
780
- display: "flex",
781
- flexDirection: "column",
782
- gap: 4,
783
- paddingLeft: 12,
784
- borderLeft: `2px solid ${sparklineColor}22`
785
- }, children: prsByDate[date].map((pr) => /* @__PURE__ */ jsxs("div", { style: {
786
- display: "flex",
787
- flexDirection: "column",
788
- gap: 5,
789
- padding: "7px 10px",
790
- borderRadius: 7,
791
- background: isDark ? "#0d0d10" : "#f7f7f9",
792
- border: `1px solid ${borderColor}`,
793
- transition: "background 0.12s"
794
- }, children: [
795
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
796
- /* @__PURE__ */ jsxs("span", { style: { fontSize: 10, fontWeight: 700, color: mutedColor, flexShrink: 0, minWidth: 36 }, children: [
797
- "#",
798
- pr.number
799
- ] }),
800
- /* @__PURE__ */ jsx("span", { style: {
801
- fontSize: 12,
802
- color: theme.palette.text.primary,
803
- flex: 1,
804
- whiteSpace: "nowrap",
805
- overflow: "hidden",
806
- textOverflow: "ellipsis"
807
- }, title: pr.title, children: pr.title }),
808
- /* @__PURE__ */ jsxs("span", { style: { fontSize: 10, color: mutedColor, flexShrink: 0 }, children: [
809
- formatDuration(pr.durationHours),
810
- " lead"
811
- ] }),
812
- /* @__PURE__ */ jsx(
813
- "a",
814
- {
815
- href: pr.url,
816
- target: "_blank",
817
- rel: "noopener noreferrer",
818
- style: { color: mutedColor, flexShrink: 0 },
819
- children: /* @__PURE__ */ jsx(ExternalLink, { size: 11 })
820
- }
821
- )
822
- ] }),
823
- pr.author && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 5 }, children: [
824
- pr.authorAvatar ? /* @__PURE__ */ jsx(
825
- "img",
826
- {
827
- src: pr.authorAvatar,
828
- alt: pr.author,
829
- style: {
830
- width: 16,
831
- height: 16,
832
- borderRadius: "50%",
833
- flexShrink: 0,
834
- border: `1px solid ${isDark ? "#3f3f46" : "#e4e4e7"}`
835
- }
836
- }
837
- ) : /* @__PURE__ */ jsx("div", { style: {
838
- width: 16,
839
- height: 16,
840
- borderRadius: "50%",
841
- flexShrink: 0,
842
- background: isDark ? "#27272a" : "#e4e4e7",
843
- display: "flex",
844
- alignItems: "center",
845
- justifyContent: "center",
846
- fontSize: 8,
847
- fontWeight: 700,
848
- color: mutedColor
849
- }, children: pr.author[0].toUpperCase() }),
850
- /* @__PURE__ */ jsxs(
851
- "a",
852
- {
853
- href: `https://github.com/${pr.author}`,
854
- target: "_blank",
855
- rel: "noopener noreferrer",
856
- style: {
857
- fontSize: 10,
858
- fontWeight: 500,
859
- color: mutedColor,
860
- textDecoration: "none",
861
- letterSpacing: "0.01em"
862
- },
863
- onMouseEnter: (e) => {
864
- e.currentTarget.style.color = "#FA6400";
865
- },
866
- onMouseLeave: (e) => {
867
- e.currentTarget.style.color = mutedColor;
868
- },
869
- children: [
870
- "@",
871
- pr.author
872
- ]
873
- }
874
- )
875
- ] })
876
- ] }, pr.number)) })
877
- ] }, date)) })
878
- ] }),
879
- !isDeployFreq && prs && prs.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
880
- /* @__PURE__ */ jsxs("div", { style: {
881
- fontSize: 10,
882
- fontWeight: 700,
883
- color: mutedColor,
884
- textTransform: "uppercase",
885
- letterSpacing: "0.12em",
886
- marginBottom: 10,
887
- borderTop: `1px solid ${borderColor}`,
888
- paddingTop: 12
889
- }, children: [
890
- title.toLowerCase().includes("restore") ? "Slowest Hotfix PRs" : "Slowest PRs",
891
- " ",
892
- "\xB7 hover a row to highlight on chart"
893
- ] }),
894
- /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: prs.map((pr, i) => {
895
- const col = durationColor(i, prs.length);
896
- const maxDur = prs[0].durationHours;
897
- const barPct = maxDur > 0 ? pr.durationHours / maxDur * 100 : 0;
898
- const isHov = pr.number === hoveredPrNumber;
899
- return /* @__PURE__ */ jsxs(
900
- "div",
901
- {
902
- ref: (el) => {
903
- if (el) prRowRefs.current.set(pr.number, el);
904
- else prRowRefs.current.delete(pr.number);
905
- },
906
- onMouseEnter: () => setHoveredPrNumber(pr.number),
907
- onMouseLeave: () => setHoveredPrNumber(null),
908
- style: {
909
- padding: "8px 12px",
910
- borderRadius: 8,
911
- background: isHov ? isDark ? "#18181f" : "#f0f0ff" : isDark ? "#111114" : "#f4f4f6",
912
- border: `1px solid ${isHov ? sparklineColor + "55" : borderColor}`,
913
- display: "flex",
914
- flexDirection: "column",
915
- gap: 5,
916
- transition: "background 0.12s, border-color 0.12s",
917
- cursor: "default"
918
- },
919
- children: [
920
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, minWidth: 0 }, children: [
921
- /* @__PURE__ */ jsxs("span", { style: { fontSize: 10, fontWeight: 700, color: col, flexShrink: 0 }, children: [
922
- "#",
923
- pr.number
924
- ] }),
925
- /* @__PURE__ */ jsx("span", { style: {
926
- fontSize: 12,
927
- color: theme.palette.text.primary,
928
- flex: 1,
929
- whiteSpace: "nowrap",
930
- overflow: "hidden",
931
- textOverflow: "ellipsis"
932
- }, title: pr.title, children: pr.title }),
933
- /* @__PURE__ */ jsx("span", { style: { fontSize: 10, color: mutedColor, flexShrink: 0 }, children: new Date(pr.mergedAt).toLocaleDateString("en-US", { month: "short", day: "numeric" }) }),
934
- /* @__PURE__ */ jsx(
935
- "a",
936
- {
937
- href: pr.url,
938
- target: "_blank",
939
- rel: "noopener noreferrer",
940
- style: { color: mutedColor, flexShrink: 0, transition: "color 0.12s" },
941
- children: /* @__PURE__ */ jsx(ExternalLink, { size: 11 })
942
- }
943
- )
944
- ] }),
945
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
946
- /* @__PURE__ */ jsx(AnimatedBar, { widthPct: barPct, color: col }),
947
- /* @__PURE__ */ jsx("span", { style: { fontSize: 11, fontWeight: 700, color: col, minWidth: 52, textAlign: "right", flexShrink: 0 }, children: formatDuration(pr.durationHours) })
948
- ] })
949
- ]
950
- },
951
- pr.number
952
- );
953
- }) })
954
- ] })
955
- ] })
956
- ] }),
957
- /* @__PURE__ */ jsx("style", { children: `
619
+ },
620
+ onMouseEnter: (e) => {
621
+ e.currentTarget.style.background = isDark ? "rgba(255,255,255,0.12)" : "rgba(0,0,0,0.1)";
622
+ },
623
+ onMouseLeave: (e) => {
624
+ e.currentTarget.style.background = isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.06)";
625
+ }
626
+ },
627
+ "\u2715"
628
+ )), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", padding: "20px 24px", display: "flex", flexDirection: "column", gap: 20 } }, weekLabels.length >= 2 ? /* @__PURE__ */ React.createElement(
629
+ DetailedChart,
630
+ {
631
+ values: sparklineValues,
632
+ weekLabels,
633
+ bucketMidMs,
634
+ color: sparklineColor,
635
+ type: sparklineType,
636
+ formatValue: formatVal,
637
+ prs: isDeployFreq ? void 0 : prs,
638
+ days,
639
+ hoveredPrNumber,
640
+ onPrHover: setHoveredPrNumber
641
+ }
642
+ ) : /* @__PURE__ */ React.createElement("div", { style: { color: mutedColor, fontSize: 13, textAlign: "center", padding: "48px 0" } }, "Not enough data \u2014 select a wider date range."), isDeployFreq && prs && prs.length > 0 && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { style: {
643
+ fontSize: 10,
644
+ fontWeight: 700,
645
+ color: mutedColor,
646
+ textTransform: "uppercase",
647
+ letterSpacing: "0.12em",
648
+ marginBottom: 12,
649
+ borderTop: `1px solid ${borderColor}`,
650
+ paddingTop: 12
651
+ } }, prListLabel ?? "All deployments", " \xB7 ", prs.length, " PR", prs.length !== 1 ? "s" : ""), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 16 } }, sortedDates.map((date) => /* @__PURE__ */ React.createElement("div", { key: date }, /* @__PURE__ */ React.createElement("div", { style: {
652
+ fontSize: 11,
653
+ fontWeight: 700,
654
+ color: sparklineColor,
655
+ marginBottom: 6,
656
+ display: "flex",
657
+ alignItems: "center",
658
+ gap: 8
659
+ } }, /* @__PURE__ */ React.createElement("span", null, date), /* @__PURE__ */ React.createElement("span", { style: { color: mutedColor, fontWeight: 500 } }, "\xB7 ", prsByDate[date].length, " PR", prsByDate[date].length > 1 ? "s" : "")), /* @__PURE__ */ React.createElement("div", { style: {
660
+ display: "flex",
661
+ flexDirection: "column",
662
+ gap: 4,
663
+ paddingLeft: 12,
664
+ borderLeft: `2px solid ${sparklineColor}22`
665
+ } }, prsByDate[date].map((pr) => /* @__PURE__ */ React.createElement("div", { key: pr.number, style: {
666
+ display: "flex",
667
+ flexDirection: "column",
668
+ gap: 5,
669
+ padding: "7px 10px",
670
+ borderRadius: 7,
671
+ background: isDark ? "#0d0d10" : "#f7f7f9",
672
+ border: `1px solid ${borderColor}`,
673
+ transition: "background 0.12s"
674
+ } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement("span", { style: { fontSize: 10, fontWeight: 700, color: mutedColor, flexShrink: 0, minWidth: 36 } }, "#", pr.number), /* @__PURE__ */ React.createElement("span", { style: {
675
+ fontSize: 12,
676
+ color: theme.palette.text.primary,
677
+ flex: 1,
678
+ whiteSpace: "nowrap",
679
+ overflow: "hidden",
680
+ textOverflow: "ellipsis"
681
+ }, title: pr.title }, pr.title), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 10, color: mutedColor, flexShrink: 0 } }, formatDuration(pr.durationHours), " lead"), /* @__PURE__ */ React.createElement(
682
+ "a",
683
+ {
684
+ href: pr.url,
685
+ target: "_blank",
686
+ rel: "noopener noreferrer",
687
+ style: { color: mutedColor, flexShrink: 0 }
688
+ },
689
+ /* @__PURE__ */ React.createElement(ExternalLink, { size: 11 })
690
+ )), pr.author && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 5 } }, pr.authorAvatar ? /* @__PURE__ */ React.createElement(
691
+ "img",
692
+ {
693
+ src: pr.authorAvatar,
694
+ alt: pr.author,
695
+ style: {
696
+ width: 16,
697
+ height: 16,
698
+ borderRadius: "50%",
699
+ flexShrink: 0,
700
+ border: `1px solid ${isDark ? "#3f3f46" : "#e4e4e7"}`
701
+ }
702
+ }
703
+ ) : /* @__PURE__ */ React.createElement("div", { style: {
704
+ width: 16,
705
+ height: 16,
706
+ borderRadius: "50%",
707
+ flexShrink: 0,
708
+ background: isDark ? "#27272a" : "#e4e4e7",
709
+ display: "flex",
710
+ alignItems: "center",
711
+ justifyContent: "center",
712
+ fontSize: 8,
713
+ fontWeight: 700,
714
+ color: mutedColor
715
+ } }, pr.author[0].toUpperCase()), /* @__PURE__ */ React.createElement(
716
+ "a",
717
+ {
718
+ href: `https://github.com/${pr.author}`,
719
+ target: "_blank",
720
+ rel: "noopener noreferrer",
721
+ style: {
722
+ fontSize: 10,
723
+ fontWeight: 500,
724
+ color: mutedColor,
725
+ textDecoration: "none",
726
+ letterSpacing: "0.01em"
727
+ },
728
+ onMouseEnter: (e) => {
729
+ e.currentTarget.style.color = "#FA6400";
730
+ },
731
+ onMouseLeave: (e) => {
732
+ e.currentTarget.style.color = mutedColor;
733
+ }
734
+ },
735
+ "@",
736
+ pr.author
737
+ ))))))))), !isDeployFreq && prs && prs.length > 0 && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { style: {
738
+ fontSize: 10,
739
+ fontWeight: 700,
740
+ color: mutedColor,
741
+ textTransform: "uppercase",
742
+ letterSpacing: "0.12em",
743
+ marginBottom: 10,
744
+ borderTop: `1px solid ${borderColor}`,
745
+ paddingTop: 12
746
+ } }, title.toLowerCase().includes("restore") ? "Slowest Hotfix PRs" : "Slowest PRs", " ", "\xB7 hover a row to highlight on chart"), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } }, prs.map((pr, i) => {
747
+ const col = durationColor(i, prs.length);
748
+ const maxDur = prs[0].durationHours;
749
+ const barPct = maxDur > 0 ? pr.durationHours / maxDur * 100 : 0;
750
+ const isHov = pr.number === hoveredPrNumber;
751
+ return /* @__PURE__ */ React.createElement(
752
+ "div",
753
+ {
754
+ key: pr.number,
755
+ ref: (el) => {
756
+ if (el) prRowRefs.current.set(pr.number, el);
757
+ else prRowRefs.current.delete(pr.number);
758
+ },
759
+ onMouseEnter: () => setHoveredPrNumber(pr.number),
760
+ onMouseLeave: () => setHoveredPrNumber(null),
761
+ style: {
762
+ padding: "8px 12px",
763
+ borderRadius: 8,
764
+ background: isHov ? isDark ? "#18181f" : "#f0f0ff" : isDark ? "#111114" : "#f4f4f6",
765
+ border: `1px solid ${isHov ? `${sparklineColor}55` : borderColor}`,
766
+ display: "flex",
767
+ flexDirection: "column",
768
+ gap: 5,
769
+ transition: "background 0.12s, border-color 0.12s",
770
+ cursor: "default"
771
+ }
772
+ },
773
+ /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6, minWidth: 0 } }, /* @__PURE__ */ React.createElement("span", { style: { fontSize: 10, fontWeight: 700, color: col, flexShrink: 0 } }, "#", pr.number), /* @__PURE__ */ React.createElement("span", { style: {
774
+ fontSize: 12,
775
+ color: theme.palette.text.primary,
776
+ flex: 1,
777
+ whiteSpace: "nowrap",
778
+ overflow: "hidden",
779
+ textOverflow: "ellipsis"
780
+ }, title: pr.title }, pr.title), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 10, color: mutedColor, flexShrink: 0 } }, new Date(pr.mergedAt).toLocaleDateString("en-US", { month: "short", day: "numeric" })), /* @__PURE__ */ React.createElement(
781
+ "a",
782
+ {
783
+ href: pr.url,
784
+ target: "_blank",
785
+ rel: "noopener noreferrer",
786
+ style: { color: mutedColor, flexShrink: 0, transition: "color 0.12s" }
787
+ },
788
+ /* @__PURE__ */ React.createElement(ExternalLink, { size: 11 })
789
+ )),
790
+ /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement(AnimatedBar, { widthPct: barPct, color: col }), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 11, fontWeight: 700, color: col, minWidth: 52, textAlign: "right", flexShrink: 0 } }, formatDuration(pr.durationHours)))
791
+ );
792
+ }))))), /* @__PURE__ */ React.createElement("style", null, `
958
793
  @keyframes detailExpandIn {
959
794
  from { opacity: 0; transform: translateX(-50%) translateY(-46%); }
960
795
  to { opacity: 1; transform: translateX(-50%) translateY(-50%); }
961
796
  }
962
- ` })
963
- ] });
797
+ `));
964
798
  }
965
799
  function AnimatedBar({ widthPct, color }) {
966
800
  const [width, setWidth] = useState(0);
@@ -971,7 +805,7 @@ function AnimatedBar({ widthPct, color }) {
971
805
  const timer = setTimeout(() => setWidth(widthPct), 80);
972
806
  return () => clearTimeout(timer);
973
807
  }, [widthPct]);
974
- return /* @__PURE__ */ jsx(
808
+ return /* @__PURE__ */ React.createElement(
975
809
  "div",
976
810
  {
977
811
  ref,
@@ -982,21 +816,21 @@ function AnimatedBar({ widthPct, color }) {
982
816
  overflow: "hidden",
983
817
  flex: 1,
984
818
  minWidth: 40
985
- },
986
- children: /* @__PURE__ */ jsx(
987
- "div",
988
- {
989
- style: {
990
- height: "100%",
991
- width: `${width}%`,
992
- background: color,
993
- borderRadius: 4,
994
- transition: "width 0.55s cubic-bezier(0.4, 0, 0.2, 1)",
995
- boxShadow: `0 0 6px ${color}88`
996
- }
819
+ }
820
+ },
821
+ /* @__PURE__ */ React.createElement(
822
+ "div",
823
+ {
824
+ style: {
825
+ height: "100%",
826
+ width: `${width}%`,
827
+ background: color,
828
+ borderRadius: 4,
829
+ transition: "width 0.55s cubic-bezier(0.4, 0, 0.2, 1)",
830
+ boxShadow: `0 0 6px ${color}88`
997
831
  }
998
- )
999
- }
832
+ }
833
+ )
1000
834
  );
1001
835
  }
1002
836
  function MetricCard({
@@ -1008,7 +842,8 @@ function MetricCard({
1008
842
  sparklineType = "line",
1009
843
  sparklineColor,
1010
844
  weekLabels,
1011
- onExpand
845
+ onExpand,
846
+ expandLabel
1012
847
  }) {
1013
848
  const theme = useTheme();
1014
849
  const isDark = theme.palette.type === "dark";
@@ -1020,76 +855,67 @@ function MetricCard({
1020
855
  const hasSparkline = sparklineData && sparklineData.length >= 2 && weekLabels && weekLabels.length >= 2;
1021
856
  const delta = hasSparkline ? buildDeltaLabel(sparklineData, metric.unit, lowerIsBetter) : null;
1022
857
  const formatVal = (v) => metric.unit === "hours" ? formatDuration(v) : `${Math.round(v * 10) / 10}`;
1023
- return /* @__PURE__ */ jsxs(
858
+ return /* @__PURE__ */ React.createElement(
1024
859
  Card,
1025
860
  {
1026
861
  style: { cursor: onExpand ? "pointer" : void 0 },
1027
862
  onClick: onExpand,
1028
863
  onMouseEnter: () => setHovered(true),
1029
- onMouseLeave: () => setHovered(false),
1030
- children: [
1031
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 8 }, children: [
1032
- /* @__PURE__ */ jsx(CardLabel, { children: title }),
1033
- onExpand && /* @__PURE__ */ jsx("span", { style: {
1034
- fontSize: 9,
1035
- fontWeight: 600,
1036
- color: isDark ? "#3f3f46" : "#d4d4d8",
1037
- opacity: hovered ? 1 : 0,
1038
- transition: "opacity 0.15s",
1039
- letterSpacing: "0.05em",
1040
- textTransform: "uppercase",
1041
- flexShrink: 0
1042
- }, children: "expand \u2197" })
1043
- ] }),
1044
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 4 }, children: [
1045
- delta && /* @__PURE__ */ jsx("span", { title: delta.tooltip, style: {
1046
- fontSize: 10,
1047
- fontWeight: 600,
1048
- cursor: "help",
1049
- color: delta.good ? "#4ade80" : "#f87171",
1050
- background: delta.good ? isDark ? "rgba(74,222,128,0.08)" : "rgba(74,222,128,0.12)" : isDark ? "rgba(248,113,113,0.08)" : "rgba(248,113,113,0.12)",
1051
- padding: "2px 8px",
1052
- borderRadius: 4,
1053
- whiteSpace: "nowrap"
1054
- }, children: delta.text }),
1055
- /* @__PURE__ */ jsx("span", { style: {
1056
- fontSize: 10,
1057
- fontWeight: 600,
1058
- color: isDark ? "#3f3f46" : "#d4d4d8",
1059
- whiteSpace: "nowrap"
1060
- }, children: lowerIsBetter ? "\u2193 lower = better" : "\u2191 higher = better" })
1061
- ] }),
1062
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "baseline", gap: 6 }, children: [
1063
- /* @__PURE__ */ jsx("span", { style: { fontSize: 36, fontWeight: 700, color: isDark ? "#f4f4f5" : "#111114", lineHeight: 1 }, children: displayValue }),
1064
- displayUnit && /* @__PURE__ */ jsx("span", { style: { fontSize: 14, color: isDark ? "#71717a" : "#71717a", fontWeight: 500 }, children: displayUnit })
1065
- ] }),
1066
- /* @__PURE__ */ jsx(RatingBadge, { rating: metric.rating }),
1067
- hasSparkline && /* @__PURE__ */ jsx("div", { style: { marginTop: 2 }, children: /* @__PURE__ */ jsx(
1068
- CompactSparkline,
1069
- {
1070
- values: sparklineData,
1071
- weekLabels,
1072
- color: sparklineColor ?? "#FA6400",
1073
- type: sparklineType,
1074
- formatValue: formatVal
1075
- }
1076
- ) }),
1077
- /* @__PURE__ */ jsx(CardDescription, { children: description }),
1078
- /* @__PURE__ */ jsx(CardFooter, { children: /* @__PURE__ */ jsx("span", { style: { fontWeight: 600, color: isDark ? "#a1a1aa" : "#52525b" }, children: targetLabel }) })
1079
- ]
1080
- }
864
+ onMouseLeave: () => setHovered(false)
865
+ },
866
+ /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 8 } }, /* @__PURE__ */ React.createElement(CardLabel, null, title), onExpand && /* @__PURE__ */ React.createElement("span", { style: {
867
+ fontSize: 9,
868
+ fontWeight: 600,
869
+ color: isDark ? "#3f3f46" : "#d4d4d8",
870
+ opacity: hovered ? 1 : 0,
871
+ transition: "opacity 0.15s",
872
+ letterSpacing: "0.05em",
873
+ textTransform: "uppercase",
874
+ flexShrink: 0
875
+ } }, "expand \u2197")),
876
+ /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 4 } }, delta && /* @__PURE__ */ React.createElement("span", { title: delta.tooltip, style: {
877
+ fontSize: 10,
878
+ fontWeight: 600,
879
+ cursor: "help",
880
+ color: delta.good ? "#4ade80" : "#f87171",
881
+ background: delta.good ? isDark ? "rgba(74,222,128,0.08)" : "rgba(74,222,128,0.12)" : isDark ? "rgba(248,113,113,0.08)" : "rgba(248,113,113,0.12)",
882
+ padding: "2px 8px",
883
+ borderRadius: 4,
884
+ whiteSpace: "nowrap"
885
+ } }, delta.text), /* @__PURE__ */ React.createElement("span", { style: {
886
+ fontSize: 10,
887
+ fontWeight: 600,
888
+ color: isDark ? "#3f3f46" : "#d4d4d8",
889
+ whiteSpace: "nowrap"
890
+ } }, lowerIsBetter ? "\u2193 lower = better" : "\u2191 higher = better")),
891
+ /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "baseline", gap: 6 } }, /* @__PURE__ */ React.createElement("span", { style: { fontSize: 36, fontWeight: 700, color: isDark ? "#f4f4f5" : "#111114", lineHeight: 1 } }, displayValue), displayUnit && /* @__PURE__ */ React.createElement("span", { style: { fontSize: 14, color: isDark ? "#71717a" : "#71717a", fontWeight: 500 } }, displayUnit)),
892
+ /* @__PURE__ */ React.createElement(RatingBadge, { rating: metric.rating }),
893
+ hasSparkline && /* @__PURE__ */ React.createElement("div", { style: { marginTop: 2 } }, /* @__PURE__ */ React.createElement(
894
+ CompactSparkline,
895
+ {
896
+ values: sparklineData,
897
+ weekLabels,
898
+ color: sparklineColor ?? "#FA6400",
899
+ type: sparklineType,
900
+ formatValue: formatVal
901
+ }
902
+ )),
903
+ /* @__PURE__ */ React.createElement(CardDescription, null, description),
904
+ /* @__PURE__ */ React.createElement(CardFooter, null, /* @__PURE__ */ React.createElement("span", { style: { fontWeight: 600, color: isDark ? "#a1a1aa" : "#52525b" } }, targetLabel), expandLabel && onExpand && /* @__PURE__ */ React.createElement("span", { style: {
905
+ fontSize: 10,
906
+ fontWeight: 700,
907
+ color: "#FA6400",
908
+ letterSpacing: "0.04em",
909
+ marginLeft: "auto"
910
+ } }, expandLabel))
1081
911
  );
1082
912
  }
1083
913
  function NaCard({ title, description }) {
1084
- return /* @__PURE__ */ jsxs(Card, { children: [
1085
- /* @__PURE__ */ jsx(CardLabel, { children: title }),
1086
- /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "baseline", gap: 6 }, children: /* @__PURE__ */ jsx("span", { style: { fontSize: 36, fontWeight: 700, color: "#52525b", lineHeight: 1 }, children: "N/A" }) }),
1087
- /* @__PURE__ */ jsx(CardDescription, { muted: true, children: description })
1088
- ] });
914
+ return /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardLabel, null, title), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "baseline", gap: 6 } }, /* @__PURE__ */ React.createElement("span", { style: { fontSize: 36, fontWeight: 700, color: "#52525b", lineHeight: 1 } }, "N/A")), /* @__PURE__ */ React.createElement(CardDescription, { muted: true }, description));
1089
915
  }
1090
916
  function FilterLabel({ children }) {
1091
917
  const theme = useTheme();
1092
- return /* @__PURE__ */ jsx("div", { style: { fontSize: 11, fontWeight: 600, color: theme.palette.text.primary, marginBottom: 4, textTransform: "uppercase", letterSpacing: "0.06em" }, children });
918
+ return /* @__PURE__ */ React.createElement("div", { style: { fontSize: 11, fontWeight: 600, color: theme.palette.text.primary, marginBottom: 4, textTransform: "uppercase", letterSpacing: "0.06em" } }, children);
1093
919
  }
1094
920
  function DoraMetricsContent() {
1095
921
  const { entity } = useEntity();
@@ -1098,12 +924,15 @@ function DoraMetricsContent() {
1098
924
  const textPrimary = muiTheme.palette.text.primary;
1099
925
  const textSecondary = muiTheme.palette.text.secondary;
1100
926
  const projectSlug = entity.metadata.annotations?.[ANNOTATION_PROJECT_SLUG] ?? "";
1101
- const environments = doraApi.getEnvironments();
927
+ const annotations = entity.metadata.annotations ?? {};
928
+ const annotationEnvs = parseAnnotationJson(annotations[ANNOTATION_ENVIRONMENTS]);
929
+ const annotationTargets = parseAnnotationJson(annotations[ANNOTATION_TARGETS]);
930
+ const environments = annotationEnvs ?? doraApi.getEnvironments();
1102
931
  const defaultEnv = environments[0];
1103
932
  const defaultDays = doraApi.getDefaultDays();
1104
- const [selectedBranch, setSelectedBranch] = useState(defaultEnv?.branch ?? "");
933
+ const [selectedEnvName, setSelectedEnvName] = useState(defaultEnv?.name ?? "");
1105
934
  const [selectedDays, setSelectedDays] = useState(String(defaultDays));
1106
- const selectedEnv = environments.find((e) => e.branch === selectedBranch) ?? defaultEnv;
935
+ const selectedEnv = environments.find((e) => e.name === selectedEnvName) ?? defaultEnv;
1107
936
  const days = parseInt(selectedDays, 10);
1108
937
  const [metrics, setMetrics] = useState(null);
1109
938
  const [loading, setLoading] = useState(true);
@@ -1111,21 +940,25 @@ function DoraMetricsContent() {
1111
940
  const [history, setHistory] = useState(null);
1112
941
  const [expanded, setExpanded] = useState(null);
1113
942
  useEffect(() => {
943
+ let cancelled = false;
1114
944
  if (!projectSlug) {
1115
945
  setError(new Error(`Entity is missing the "${ANNOTATION_PROJECT_SLUG}" annotation.`));
1116
946
  setLoading(false);
1117
- return;
947
+ return () => {
948
+ cancelled = true;
949
+ };
1118
950
  }
1119
951
  if (!selectedEnv) {
1120
952
  setError(new Error("No environments configured for DORA metrics."));
1121
953
  setLoading(false);
1122
- return;
954
+ return () => {
955
+ cancelled = true;
956
+ };
1123
957
  }
1124
- let cancelled = false;
1125
958
  setLoading(true);
1126
959
  setError(null);
1127
960
  setHistory(null);
1128
- doraApi.getMetrics(projectSlug, selectedEnv.branch, selectedEnv.isProduction, selectedEnv.label, days).then((data) => {
961
+ doraApi.getMetrics(projectSlug, selectedEnv, days, annotationTargets ?? void 0).then((data) => {
1129
962
  if (!cancelled) {
1130
963
  setMetrics(data);
1131
964
  setLoading(false);
@@ -1136,175 +969,147 @@ function DoraMetricsContent() {
1136
969
  setLoading(false);
1137
970
  }
1138
971
  });
1139
- doraApi.getHistory(projectSlug, selectedEnv.branch, selectedEnv.isProduction, selectedEnv.label, days).then((data) => {
972
+ doraApi.getHistory(projectSlug, selectedEnv, days).then((data) => {
1140
973
  if (!cancelled) setHistory(data);
1141
974
  }).catch(() => {
1142
975
  });
1143
976
  return () => {
1144
977
  cancelled = true;
1145
978
  };
1146
- }, [doraApi, projectSlug, selectedEnv?.branch, days]);
1147
- if (loading) return /* @__PURE__ */ jsx(DojoLoadingOverlay, {});
979
+ }, [doraApi, projectSlug, selectedEnv?.name, days, annotationTargets]);
980
+ if (loading) return /* @__PURE__ */ React.createElement(DoraLoadingOverlay, null);
1148
981
  if (error) {
1149
- return /* @__PURE__ */ jsx(InfoCard, { title: "DORA Metrics", children: /* @__PURE__ */ jsxs("div", { style: { padding: 24, color: "#f87171" }, children: [
1150
- /* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: "Error: " }),
1151
- error.message
1152
- ] }) });
982
+ return /* @__PURE__ */ React.createElement(InfoCard, { title: "DORA Metrics" }, /* @__PURE__ */ React.createElement("div", { style: { padding: 24, color: "#f87171" } }, /* @__PURE__ */ React.createElement("span", { style: { fontWeight: 600 } }, "Error: "), error.message));
1153
983
  }
1154
984
  if (!metrics) return null;
1155
- return /* @__PURE__ */ jsx(InfoCard, { title: " ", children: /* @__PURE__ */ jsxs("div", { style: { padding: "4px 0", position: "relative" }, children: [
1156
- expanded && /* @__PURE__ */ jsx(DetailedOverlay, { data: expanded, days, onClose: () => setExpanded(null) }),
1157
- /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
1158
- /* @__PURE__ */ jsx("div", { style: { fontSize: 22, fontWeight: 700, color: textPrimary, letterSpacing: "-0.02em" }, children: "DORA Metrics" }),
1159
- selectedEnv && /* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: textSecondary, marginTop: 5 }, children: [
1160
- "Showing metrics for branch",
1161
- " ",
1162
- /* @__PURE__ */ jsx("strong", { style: { color: textPrimary, fontFamily: "monospace" }, children: selectedEnv.branch })
1163
- ] })
1164
- ] }),
1165
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "flex-end", gap: 16, flexWrap: "wrap", marginBottom: 20 }, children: [
1166
- environments.length > 1 && /* @__PURE__ */ jsxs("div", { children: [
1167
- /* @__PURE__ */ jsx(FilterLabel, { children: "Branch" }),
1168
- /* @__PURE__ */ jsxs(Select, { value: selectedBranch, onValueChange: setSelectedBranch, children: [
1169
- /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select branch" }) }),
1170
- /* @__PURE__ */ jsx(SelectContent, { children: environments.map((env) => /* @__PURE__ */ jsxs(SelectItem, { value: env.branch, children: [
1171
- env.branch,
1172
- " (",
1173
- env.name,
1174
- ")"
1175
- ] }, env.branch)) })
1176
- ] })
1177
- ] }),
1178
- /* @__PURE__ */ jsxs("div", { children: [
1179
- /* @__PURE__ */ jsx(FilterLabel, { children: "Date Range" }),
1180
- /* @__PURE__ */ jsxs(Select, { value: selectedDays, onValueChange: setSelectedDays, children: [
1181
- /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select date range" }) }),
1182
- /* @__PURE__ */ jsx(SelectContent, { children: DATE_RANGE_OPTIONS.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })
1183
- ] })
1184
- ] })
1185
- ] }),
1186
- (() => {
1187
- const wkLabels = history?.map((h) => h.weekLabel) ?? [];
1188
- const deployData = history?.map((h) => h.deploymentCount) ?? [];
1189
- const leadData = history?.map((h) => h.leadTimeHours) ?? [];
1190
- const cfrData = history?.map((h) => h.changeFailureRate ?? 0) ?? [];
1191
- const mttrData = history?.map((h) => h.mttrHours ?? 0) ?? [];
1192
- const bmMs = history?.map((h) => h.bucketMidMs) ?? [];
1193
- const mkExpand = (title, metric, description, lowerIsBetter, sparklineValues, sparklineType, sparklineColor, prs) => () => setExpanded({
1194
- title,
1195
- metric,
1196
- description,
1197
- lowerIsBetter,
1198
- sparklineValues,
1199
- sparklineType,
1200
- sparklineColor,
985
+ return /* @__PURE__ */ React.createElement(InfoCard, { title: " " }, /* @__PURE__ */ React.createElement("div", { style: { padding: "4px 0", position: "relative" } }, expanded && /* @__PURE__ */ React.createElement(DetailedOverlay, { data: expanded, days, onClose: () => setExpanded(null) }), /* @__PURE__ */ React.createElement("div", { style: { marginBottom: 16 } }, /* @__PURE__ */ React.createElement("div", { style: { fontSize: 22, fontWeight: 700, color: textPrimary, letterSpacing: "-0.02em" } }, "DORA Metrics"), selectedEnv && /* @__PURE__ */ React.createElement("div", { style: { fontSize: 12, color: textSecondary, marginTop: 5 } }, "Showing metrics for", " ", /* @__PURE__ */ React.createElement("strong", { style: { color: textPrimary } }, selectedEnv.name), " ", "\u2014", " ", /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "monospace" } }, selectedEnv.branch))), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "flex-end", gap: 16, flexWrap: "wrap", marginBottom: 20 } }, environments.length > 1 && /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(FilterLabel, null, "Environment"), /* @__PURE__ */ React.createElement(Select, { value: selectedEnvName, onValueChange: setSelectedEnvName }, /* @__PURE__ */ React.createElement(SelectTrigger, null, /* @__PURE__ */ React.createElement(SelectValue, { placeholder: "Select environment" })), /* @__PURE__ */ React.createElement(SelectContent, null, environments.map((env) => /* @__PURE__ */ React.createElement(SelectItem, { key: env.name, value: env.name }, env.name, " (", env.branch, ")"))))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(FilterLabel, null, "Date Range"), /* @__PURE__ */ React.createElement(Select, { value: selectedDays, onValueChange: setSelectedDays }, /* @__PURE__ */ React.createElement(SelectTrigger, null, /* @__PURE__ */ React.createElement(SelectValue, { placeholder: "Select date range" })), /* @__PURE__ */ React.createElement(SelectContent, null, DATE_RANGE_OPTIONS.map((opt) => /* @__PURE__ */ React.createElement(SelectItem, { key: opt.value, value: opt.value }, opt.label)))))), (() => {
986
+ const wkLabels = history?.map((h) => h.weekLabel) ?? [];
987
+ const deployData = history?.map((h) => h.deploymentCount) ?? [];
988
+ const leadData = history?.map((h) => h.leadTimeHours) ?? [];
989
+ const cfrData = history?.map((h) => h.changeFailureRate ?? 0) ?? [];
990
+ const mttrData = history?.map((h) => h.mttrHours ?? 0) ?? [];
991
+ const bmMs = history?.map((h) => h.bucketMidMs) ?? [];
992
+ const mkExpand = (title, metric, description, lowerIsBetter, sparklineValues, sparklineType, sparklineColor, prs, prListLabel) => () => setExpanded({
993
+ title,
994
+ metric,
995
+ description,
996
+ lowerIsBetter,
997
+ sparklineValues,
998
+ sparklineType,
999
+ sparklineColor,
1000
+ weekLabels: wkLabels,
1001
+ bucketMidMs: bmMs,
1002
+ prs,
1003
+ prListLabel
1004
+ });
1005
+ return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexWrap: "wrap", gap: 12 } }, /* @__PURE__ */ React.createElement(
1006
+ MetricCard,
1007
+ {
1008
+ title: "Deployment Frequency",
1009
+ metric: metrics.deploymentFrequency,
1010
+ description: "How often code is deployed to this branch",
1011
+ sparklineData: deployData,
1012
+ sparklineType: "bar",
1013
+ sparklineColor: "#FA6400",
1201
1014
  weekLabels: wkLabels,
1202
- bucketMidMs: bmMs,
1203
- prs
1204
- });
1205
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: 12 }, children: [
1206
- /* @__PURE__ */ jsx(
1207
- MetricCard,
1208
- {
1209
- title: "Deployment Frequency",
1210
- metric: metrics.deploymentFrequency,
1211
- description: "How often code is deployed to this branch",
1212
- sparklineData: deployData,
1213
- sparklineType: "bar",
1214
- sparklineColor: "#FA6400",
1215
- weekLabels: wkLabels,
1216
- onExpand: mkExpand(
1217
- "Deployment Frequency",
1218
- metrics.deploymentFrequency,
1219
- "How often code is deployed to this branch",
1220
- false,
1221
- deployData,
1222
- "bar",
1223
- "#FA6400",
1224
- metrics.deploymentFrequency.slowestPRs
1225
- )
1226
- }
1227
- ),
1228
- /* @__PURE__ */ jsx(
1229
- MetricCard,
1230
- {
1231
- title: "Lead Time for Changes",
1232
- metric: metrics.leadTime,
1233
- description: "Average time from PR creation to merge",
1234
- lowerIsBetter: true,
1235
- sparklineData: leadData,
1236
- sparklineType: "line",
1237
- sparklineColor: "#818cf8",
1238
- weekLabels: wkLabels,
1239
- onExpand: mkExpand(
1240
- "Lead Time for Changes",
1241
- metrics.leadTime,
1242
- "Average time from PR creation to merge",
1243
- true,
1244
- leadData,
1245
- "line",
1246
- "#818cf8",
1247
- metrics.leadTime.slowestPRs
1248
- )
1249
- }
1250
- ),
1251
- metrics.changeFailureRate !== null ? /* @__PURE__ */ jsx(
1252
- MetricCard,
1253
- {
1254
- title: "Change Failure Rate",
1255
- metric: metrics.changeFailureRate,
1256
- description: "% of all merged PRs that were hotfixes",
1257
- lowerIsBetter: true,
1258
- sparklineData: cfrData,
1259
- sparklineType: "line",
1260
- sparklineColor: "#f87171",
1261
- weekLabels: wkLabels,
1262
- onExpand: mkExpand(
1263
- "Change Failure Rate",
1264
- metrics.changeFailureRate,
1265
- "% of all merged PRs that were hotfixes",
1266
- true,
1267
- cfrData,
1268
- "line",
1269
- "#f87171"
1270
- )
1271
- }
1272
- ) : /* @__PURE__ */ jsx(NaCard, { title: "Change Failure Rate", description: "Only tracked on production branches" }),
1273
- metrics.mttr !== null ? /* @__PURE__ */ jsx(
1274
- MetricCard,
1275
- {
1276
- title: "Mean Time to Restore",
1277
- metric: metrics.mttr,
1278
- description: "Avg time from hotfix PR open to merge",
1279
- lowerIsBetter: true,
1280
- sparklineData: mttrData,
1281
- sparklineType: "line",
1282
- sparklineColor: "#fbbf24",
1283
- weekLabels: wkLabels,
1284
- onExpand: mkExpand(
1285
- "Mean Time to Restore",
1286
- metrics.mttr,
1287
- "Avg time from hotfix PR open to merge",
1288
- true,
1289
- mttrData,
1290
- "line",
1291
- "#fbbf24",
1292
- metrics.mttr.slowestPRs
1293
- )
1294
- }
1295
- ) : /* @__PURE__ */ jsx(NaCard, { title: "Mean Time to Restore", description: "Only tracked on production branches" }),
1296
- metrics.numberOfHotfixes !== null ? /* @__PURE__ */ jsx(
1297
- MetricCard,
1298
- {
1299
- title: "Hotfixes to Production",
1300
- metric: metrics.numberOfHotfixes,
1301
- description: `PRs labeled '${selectedEnv?.label ?? "hotfix"}' merged to production`,
1302
- lowerIsBetter: true
1303
- }
1304
- ) : /* @__PURE__ */ jsx(NaCard, { title: "Hotfixes to Production", description: "Only tracked on production branches" })
1305
- ] });
1306
- })()
1307
- ] }) });
1015
+ onExpand: mkExpand(
1016
+ "Deployment Frequency",
1017
+ metrics.deploymentFrequency,
1018
+ "How often code is deployed to this branch",
1019
+ false,
1020
+ deployData,
1021
+ "bar",
1022
+ "#FA6400",
1023
+ metrics.deploymentFrequency.slowestPRs
1024
+ )
1025
+ }
1026
+ ), /* @__PURE__ */ React.createElement(
1027
+ MetricCard,
1028
+ {
1029
+ title: "Lead Time for Changes",
1030
+ metric: metrics.leadTime,
1031
+ description: "Average time from PR creation to merge",
1032
+ lowerIsBetter: true,
1033
+ sparklineData: leadData,
1034
+ sparklineType: "line",
1035
+ sparklineColor: "#818cf8",
1036
+ weekLabels: wkLabels,
1037
+ onExpand: mkExpand(
1038
+ "Lead Time for Changes",
1039
+ metrics.leadTime,
1040
+ "Average time from PR creation to merge",
1041
+ true,
1042
+ leadData,
1043
+ "line",
1044
+ "#818cf8",
1045
+ metrics.leadTime.slowestPRs
1046
+ )
1047
+ }
1048
+ ), metrics.changeFailureRate !== null ? /* @__PURE__ */ React.createElement(
1049
+ MetricCard,
1050
+ {
1051
+ title: "Change Failure Rate",
1052
+ metric: metrics.changeFailureRate,
1053
+ description: "% of all merged PRs that were hotfixes",
1054
+ lowerIsBetter: true,
1055
+ sparklineData: cfrData,
1056
+ sparklineType: "line",
1057
+ sparklineColor: "#f87171",
1058
+ weekLabels: wkLabels,
1059
+ onExpand: mkExpand(
1060
+ "Change Failure Rate",
1061
+ metrics.changeFailureRate,
1062
+ "% of all merged PRs that were hotfixes",
1063
+ true,
1064
+ cfrData,
1065
+ "line",
1066
+ "#f87171"
1067
+ )
1068
+ }
1069
+ ) : /* @__PURE__ */ React.createElement(NaCard, { title: "Change Failure Rate", description: "Only tracked on production branches" }), metrics.mttr !== null ? /* @__PURE__ */ React.createElement(
1070
+ MetricCard,
1071
+ {
1072
+ title: "Mean Time to Restore",
1073
+ metric: metrics.mttr,
1074
+ description: "Avg time from hotfix PR open to merge",
1075
+ lowerIsBetter: true,
1076
+ sparklineData: mttrData,
1077
+ sparklineType: "line",
1078
+ sparklineColor: "#fbbf24",
1079
+ weekLabels: wkLabels,
1080
+ onExpand: mkExpand(
1081
+ "Mean Time to Restore",
1082
+ metrics.mttr,
1083
+ "Avg time from hotfix PR open to merge",
1084
+ true,
1085
+ mttrData,
1086
+ "line",
1087
+ "#fbbf24",
1088
+ metrics.mttr.slowestPRs
1089
+ )
1090
+ }
1091
+ ) : /* @__PURE__ */ React.createElement(NaCard, { title: "Mean Time to Restore", description: "Only tracked on production branches" }), metrics.numberOfHotfixes !== null ? /* @__PURE__ */ React.createElement(
1092
+ MetricCard,
1093
+ {
1094
+ title: "Hotfixes to Production",
1095
+ metric: metrics.numberOfHotfixes,
1096
+ description: `PRs labeled '${selectedEnv?.label ?? "hotfix"}' merged to production`,
1097
+ lowerIsBetter: true,
1098
+ expandLabel: metrics.numberOfHotfixes.value > 0 ? `View ${metrics.numberOfHotfixes.value} PR${metrics.numberOfHotfixes.value !== 1 ? "s" : ""} \u2192` : void 0,
1099
+ onExpand: mkExpand(
1100
+ "Hotfixes to Production",
1101
+ metrics.numberOfHotfixes,
1102
+ `PRs labeled '${selectedEnv?.label ?? "hotfix"}' merged to production`,
1103
+ true,
1104
+ [],
1105
+ "bar",
1106
+ "#f87171",
1107
+ metrics.numberOfHotfixes.slowestPRs,
1108
+ "All hotfixes"
1109
+ )
1110
+ }
1111
+ ) : /* @__PURE__ */ React.createElement(NaCard, { title: "Hotfixes to Production", description: "Only tracked on production branches" }));
1112
+ })()));
1308
1113
  }
1309
1114
 
1310
1115
  export { DoraMetricsContent };