@kaizenreport/kensho-viewer 0.1.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/LICENSE +21 -0
- package/README.md +19 -0
- package/assets/app.js +1396 -0
- package/assets/app.jsx +860 -0
- package/assets/charts.js +1156 -0
- package/assets/charts.jsx +593 -0
- package/assets/colors_and_type.css +219 -0
- package/assets/components.js +894 -0
- package/assets/components.jsx +520 -0
- package/assets/data-bridge.js +49 -0
- package/assets/data-bridge.jsx +55 -0
- package/assets/data-loader.js +543 -0
- package/assets/kaizen-mark.svg +5 -0
- package/assets/kensho-wordmark.svg +18 -0
- package/assets/pages.js +1472 -0
- package/assets/pages.jsx +822 -0
- package/assets/test-detail.js +1058 -0
- package/assets/test-detail.jsx +502 -0
- package/assets/tokens.css +357 -0
- package/assets/tree-detail.js +1705 -0
- package/assets/tree-detail.jsx +947 -0
- package/dist/component.d.ts +115 -0
- package/dist/component.js +698 -0
- package/index.html +35 -0
- package/package.json +65 -0
- package/scripts/build.js +117 -0
- package/src/component.d.ts +115 -0
- package/src/component.jsx +340 -0
- package/src/data.js +538 -0
|
@@ -0,0 +1,894 @@
|
|
|
1
|
+
/* Auto-generated from components.jsx by packages/viewer/scripts/build.js. Edit the .jsx — DO NOT edit this file. */
|
|
2
|
+
/* global React */
|
|
3
|
+
const {
|
|
4
|
+
useState
|
|
5
|
+
} = React;
|
|
6
|
+
|
|
7
|
+
// Stable no-op context for the static-report path. Hooks must be called
|
|
8
|
+
// unconditionally; passing this when window.__KenshoContext is undefined
|
|
9
|
+
// keeps consumers seeing `null` and behaving exactly as the static OSS
|
|
10
|
+
// report did before the embed refactor.
|
|
11
|
+
const _kvCompNullCtx = React.createContext(null);
|
|
12
|
+
|
|
13
|
+
// Icon wrapper — renders the SVG directly from lucide's icon registry rather
|
|
14
|
+
// than dropping an `<i data-lucide="…">` placeholder + relying on a global
|
|
15
|
+
// `lucide.createIcons()` pass. The global pass scans the whole document and
|
|
16
|
+
// rewrites <i> elements anywhere it finds them — fine for the static report
|
|
17
|
+
// (we own the page) but breaks when the viewer is embedded in a host page
|
|
18
|
+
// that uses its own icon library: lucide rewrites the host's <i> tags, the
|
|
19
|
+
// host's React reconciler then crashes with "removeChild: not a child of
|
|
20
|
+
// this node". Self-contained inline SVG sidesteps the whole class of bugs.
|
|
21
|
+
const _kvIconCache = new Map();
|
|
22
|
+
// Inline-SVG fallbacks for the icons the viewer cares about, used when the
|
|
23
|
+
// lucide UMD bundle either failed to load or rearranged its registry shape.
|
|
24
|
+
// Without this fallback the theme-toggle, export, and run-id chip render as
|
|
25
|
+
// blank squares — the user sees an "empty box" between the breadcrumb and
|
|
26
|
+
// the Export button.
|
|
27
|
+
const _KV_ICON_FALLBACKS = {
|
|
28
|
+
'sun': '<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/>',
|
|
29
|
+
'moon': '<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>',
|
|
30
|
+
'download': '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>',
|
|
31
|
+
'external-link': '<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/>',
|
|
32
|
+
'chevron-right': '<polyline points="9 18 15 12 9 6"/>',
|
|
33
|
+
'chevron-down': '<polyline points="6 9 12 15 18 9"/>',
|
|
34
|
+
'chevron-up': '<polyline points="18 15 12 9 6 15"/>',
|
|
35
|
+
'chevron-left': '<polyline points="15 18 9 12 15 6"/>',
|
|
36
|
+
'circle': '<circle cx="12" cy="12" r="10"/>',
|
|
37
|
+
'layout-dashboard': '<rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/>',
|
|
38
|
+
'folder-tree': '<path d="M20 10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-2.5a1 1 0 0 1-.8-.4l-.9-1.2A1 1 0 0 0 15 3h-2a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z"/><path d="M20 21a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1h-2.9a1 1 0 0 1-.88-.55l-.42-.85a1 1 0 0 0-.92-.6H13a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z"/><path d="M3 5a2 2 0 0 0 2 2h3"/><path d="M3 3v13a2 2 0 0 0 2 2h3"/>',
|
|
39
|
+
'bar-chart-3': '<path d="M3 3v18h18"/><path d="M18 17V9"/><path d="M13 17V5"/><path d="M8 17v-3"/>',
|
|
40
|
+
'clock': '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
|
|
41
|
+
'tags': '<path d="m20.59 13.41-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/>',
|
|
42
|
+
'activity': '<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>',
|
|
43
|
+
'list-tree': '<path d="M21 12h-8"/><path d="M21 6H8"/><path d="M21 18h-8"/><path d="M3 6v4c0 1.1.9 2 2 2h3"/><path d="M3 10v6c0 1.1.9 2 2 2h3"/>',
|
|
44
|
+
'package': '<path d="m7.5 4.27 9 5.15"/><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>',
|
|
45
|
+
'history': '<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M12 7v5l4 2"/>'
|
|
46
|
+
};
|
|
47
|
+
function _kvFallbackSvg(name, size) {
|
|
48
|
+
const inner = _KV_ICON_FALLBACKS[name];
|
|
49
|
+
if (!inner) return null;
|
|
50
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${inner}</svg>`;
|
|
51
|
+
}
|
|
52
|
+
function lucideSvg(name, size) {
|
|
53
|
+
const cacheKey = name + ':' + size;
|
|
54
|
+
if (_kvIconCache.has(cacheKey)) return _kvIconCache.get(cacheKey);
|
|
55
|
+
// Prefer the inline fallback — lucide's UMD bundle has shifted shapes
|
|
56
|
+
// across versions (sometimes [tag,attrs,children], sometimes a string,
|
|
57
|
+
// sometimes an icon factory). The fallback set covers every icon the
|
|
58
|
+
// viewer renders, so we use it deterministically.
|
|
59
|
+
const fallback = _kvFallbackSvg(name, size);
|
|
60
|
+
if (fallback) {
|
|
61
|
+
_kvIconCache.set(cacheKey, fallback);
|
|
62
|
+
return fallback;
|
|
63
|
+
}
|
|
64
|
+
let svg = '';
|
|
65
|
+
try {
|
|
66
|
+
const reg = window.lucide && (window.lucide.icons || window.lucide);
|
|
67
|
+
const pascal = name.split('-').map(s => s[0].toUpperCase() + s.slice(1)).join('');
|
|
68
|
+
const node = reg && (reg[pascal] || reg[name]);
|
|
69
|
+
if (node && Array.isArray(node)) {
|
|
70
|
+
const [tag, attrs, children] = node;
|
|
71
|
+
const attrStr = Object.entries({
|
|
72
|
+
...attrs,
|
|
73
|
+
width: size,
|
|
74
|
+
height: size
|
|
75
|
+
}).map(([k, v]) => `${k}="${v}"`).join(' ');
|
|
76
|
+
const inner = (children || []).map(c => {
|
|
77
|
+
const [ct, ca] = c;
|
|
78
|
+
const at = Object.entries(ca).map(([k, v]) => `${k}="${v}"`).join(' ');
|
|
79
|
+
return `<${ct} ${at}/>`;
|
|
80
|
+
}).join('');
|
|
81
|
+
if (inner) svg = `<${tag} ${attrStr}>${inner}</${tag}>`;
|
|
82
|
+
}
|
|
83
|
+
} catch (_e) {}
|
|
84
|
+
if (!svg) svg = `<svg width="${size}" height="${size}"></svg>`;
|
|
85
|
+
_kvIconCache.set(cacheKey, svg);
|
|
86
|
+
return svg;
|
|
87
|
+
}
|
|
88
|
+
const Icon = ({
|
|
89
|
+
name,
|
|
90
|
+
size = 16
|
|
91
|
+
}) => /*#__PURE__*/React.createElement("span", {
|
|
92
|
+
style: {
|
|
93
|
+
display: 'inline-flex',
|
|
94
|
+
alignItems: 'center',
|
|
95
|
+
justifyContent: 'center',
|
|
96
|
+
width: size,
|
|
97
|
+
height: size,
|
|
98
|
+
lineHeight: 0,
|
|
99
|
+
verticalAlign: 'middle'
|
|
100
|
+
},
|
|
101
|
+
dangerouslySetInnerHTML: {
|
|
102
|
+
__html: lucideSvg(name, size)
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
function Sidebar({
|
|
106
|
+
active,
|
|
107
|
+
onNav
|
|
108
|
+
}) {
|
|
109
|
+
// Show a small badge next to "Flaky" when this run has flaky signals
|
|
110
|
+
// (retries > 0 or status === 'broken'). Helps users discover the feature.
|
|
111
|
+
const flakyCount = Object.values(window.RICH_TESTS || {}).filter(t => t.retries > 0 || t.status === 'broken').length;
|
|
112
|
+
const items = [['overview', 'Overview', 'layout-dashboard'], ['suites', 'Suites', 'folder-tree'], ['graphs', 'Graphs', 'bar-chart-3'], ['timeline', 'Timeline', 'clock'], ['categories', 'Categories', 'tags'], ['flaky', 'Flaky', 'activity', flakyCount], ['behaviors', 'Behaviors', 'list-tree'], ['packages', 'Packages', 'package'], ['history', 'History', 'history']];
|
|
113
|
+
// Pull host-provided extras from the embed context, if present. In the
|
|
114
|
+
// static-report path window.__KenshoContext is undefined → useContext on
|
|
115
|
+
// the local null-ctx returns null → no extras, sidebar renders as before.
|
|
116
|
+
const ctx = React.useContext(window.__KenshoContext || _kvCompNullCtx);
|
|
117
|
+
const extras = ctx?.extraSidebar || [];
|
|
118
|
+
return /*#__PURE__*/React.createElement("aside", {
|
|
119
|
+
className: "sb"
|
|
120
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
121
|
+
className: "brand"
|
|
122
|
+
}, /*#__PURE__*/React.createElement("img", {
|
|
123
|
+
src: (window.__KENSHO_ASSETS_BASE || 'assets/') + 'kaizen-mark.svg',
|
|
124
|
+
alt: "",
|
|
125
|
+
style: {
|
|
126
|
+
width: 28,
|
|
127
|
+
height: 28
|
|
128
|
+
}
|
|
129
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
130
|
+
className: "name",
|
|
131
|
+
style: {
|
|
132
|
+
display: 'inline-flex',
|
|
133
|
+
alignItems: 'baseline',
|
|
134
|
+
gap: 8
|
|
135
|
+
}
|
|
136
|
+
}, "Kensho", /*#__PURE__*/React.createElement("span", {
|
|
137
|
+
className: "accent"
|
|
138
|
+
}, "\xB7"), /*#__PURE__*/React.createElement("span", {
|
|
139
|
+
style: {
|
|
140
|
+
fontSize: 13,
|
|
141
|
+
fontWeight: 500,
|
|
142
|
+
color: 'var(--brand-green-400)',
|
|
143
|
+
opacity: 0.85,
|
|
144
|
+
fontFamily: 'sans-serif',
|
|
145
|
+
letterSpacing: 0
|
|
146
|
+
},
|
|
147
|
+
title: "\u6539\u5584 \xB7 kaizen \u2014 continuous improvement"
|
|
148
|
+
}, "\u6539\u5584"))), /*#__PURE__*/React.createElement("nav", null, items.map(([id, label, icon, badge]) => /*#__PURE__*/React.createElement("a", {
|
|
149
|
+
key: id,
|
|
150
|
+
className: active === id ? 'active' : '',
|
|
151
|
+
onClick: () => onNav(id)
|
|
152
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
153
|
+
name: icon,
|
|
154
|
+
size: 18
|
|
155
|
+
}), /*#__PURE__*/React.createElement("span", {
|
|
156
|
+
style: {
|
|
157
|
+
flex: 1
|
|
158
|
+
}
|
|
159
|
+
}, label), badge > 0 && /*#__PURE__*/React.createElement("span", {
|
|
160
|
+
style: {
|
|
161
|
+
fontFamily: 'var(--font-mono)',
|
|
162
|
+
fontSize: 10,
|
|
163
|
+
fontWeight: 700,
|
|
164
|
+
padding: '1px 6px',
|
|
165
|
+
borderRadius: 999,
|
|
166
|
+
background: 'var(--status-broken-bg)',
|
|
167
|
+
color: 'var(--status-broken-fg)',
|
|
168
|
+
border: '1px solid var(--status-broken-border)',
|
|
169
|
+
lineHeight: 1.4
|
|
170
|
+
}
|
|
171
|
+
}, badge))), extras.length > 0 && /*#__PURE__*/React.createElement("div", {
|
|
172
|
+
style: {
|
|
173
|
+
height: 1,
|
|
174
|
+
background: 'var(--dark-line)',
|
|
175
|
+
margin: '6px 12px'
|
|
176
|
+
}
|
|
177
|
+
}), extras.map(ex => /*#__PURE__*/React.createElement("a", {
|
|
178
|
+
key: ex.id,
|
|
179
|
+
className: active === ex.id ? 'active' : '',
|
|
180
|
+
onClick: () => onNav(ex.id)
|
|
181
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
182
|
+
name: ex.icon || 'circle',
|
|
183
|
+
size: 18
|
|
184
|
+
}), /*#__PURE__*/React.createElement("span", {
|
|
185
|
+
style: {
|
|
186
|
+
flex: 1
|
|
187
|
+
}
|
|
188
|
+
}, ex.label)))), /*#__PURE__*/React.createElement("div", {
|
|
189
|
+
className: "foot"
|
|
190
|
+
}, "v0.4.2 \xB7 build 28a91f"));
|
|
191
|
+
}
|
|
192
|
+
function ThemeToggle() {
|
|
193
|
+
const [theme, setTheme] = React.useState(() => {
|
|
194
|
+
try {
|
|
195
|
+
return localStorage.getItem('kensho-theme') || 'light';
|
|
196
|
+
} catch (e) {
|
|
197
|
+
return 'light';
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
React.useEffect(() => {
|
|
201
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
202
|
+
try {
|
|
203
|
+
localStorage.setItem('kensho-theme', theme);
|
|
204
|
+
} catch (e) {}
|
|
205
|
+
// No global lucide rewrite — Icon renders inline SVG (see top of file).
|
|
206
|
+
}, [theme]);
|
|
207
|
+
const next = theme === 'dark' ? 'light' : 'dark';
|
|
208
|
+
return /*#__PURE__*/React.createElement("button", {
|
|
209
|
+
className: "theme-toggle",
|
|
210
|
+
onClick: () => setTheme(next),
|
|
211
|
+
title: `Switch to ${next} theme`,
|
|
212
|
+
"aria-label": `Switch to ${next} theme`
|
|
213
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
214
|
+
name: theme === 'dark' ? 'sun' : 'moon',
|
|
215
|
+
size: 15
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
function TopBar({
|
|
219
|
+
crumbs,
|
|
220
|
+
onRerun,
|
|
221
|
+
project
|
|
222
|
+
}) {
|
|
223
|
+
const RUN = window.RUN;
|
|
224
|
+
const failed = (RUN?.counts?.failed || 0) + (RUN?.counts?.broken || 0) > 0;
|
|
225
|
+
const branch = RUN?.branch || 'local';
|
|
226
|
+
const id = RUN?.id || '';
|
|
227
|
+
const runUrl = RUN?.runUrl || '';
|
|
228
|
+
|
|
229
|
+
// Export — fetch data/index.json and trigger a download.
|
|
230
|
+
// Honors gzip via the browser's Accept-Encoding (Python http.server doesn't
|
|
231
|
+
// emit Content-Encoding for .json so we just save the raw bytes).
|
|
232
|
+
const onExport = async () => {
|
|
233
|
+
try {
|
|
234
|
+
const res = await fetch('data/index.json', {
|
|
235
|
+
cache: 'no-cache'
|
|
236
|
+
});
|
|
237
|
+
const blob = await res.blob();
|
|
238
|
+
const url = URL.createObjectURL(blob);
|
|
239
|
+
const a = document.createElement('a');
|
|
240
|
+
a.href = url;
|
|
241
|
+
a.download = `kensho-${(RUN?.id || 'run').replace(/^#/, '')}.json`;
|
|
242
|
+
document.body.appendChild(a);
|
|
243
|
+
a.click();
|
|
244
|
+
a.remove();
|
|
245
|
+
URL.revokeObjectURL(url);
|
|
246
|
+
} catch (e) {
|
|
247
|
+
console.error('[kensho] export failed:', e);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
251
|
+
className: "tb"
|
|
252
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
253
|
+
className: "crumb"
|
|
254
|
+
}, crumbs.map((c, i) => /*#__PURE__*/React.createElement(React.Fragment, {
|
|
255
|
+
key: i
|
|
256
|
+
}, i > 0 && /*#__PURE__*/React.createElement(Icon, {
|
|
257
|
+
name: "chevron-right",
|
|
258
|
+
size: 14
|
|
259
|
+
}), i === crumbs.length - 1 ? /*#__PURE__*/React.createElement("b", null, c) : /*#__PURE__*/React.createElement("span", null, c)))), /*#__PURE__*/React.createElement("div", {
|
|
260
|
+
className: "grow"
|
|
261
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
262
|
+
className: `runsel${failed ? ' fail' : ''}`,
|
|
263
|
+
title: `${branch} · ${id} · click to copy run id`,
|
|
264
|
+
onClick: () => navigator.clipboard?.writeText(id.replace(/^#/, '')),
|
|
265
|
+
style: {
|
|
266
|
+
cursor: 'pointer'
|
|
267
|
+
}
|
|
268
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
269
|
+
className: "dot"
|
|
270
|
+
}), /*#__PURE__*/React.createElement("span", null, branch, " \xB7 ", id)), /*#__PURE__*/React.createElement(ThemeToggle, null), /*#__PURE__*/React.createElement("button", {
|
|
271
|
+
className: "btn btn-secondary",
|
|
272
|
+
onClick: onExport,
|
|
273
|
+
title: "Download data/index.json"
|
|
274
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
275
|
+
name: "download",
|
|
276
|
+
size: 14
|
|
277
|
+
}), "Export"), failed && runUrl && /*#__PURE__*/React.createElement("a", {
|
|
278
|
+
className: "btn btn-primary",
|
|
279
|
+
href: runUrl,
|
|
280
|
+
target: "_blank",
|
|
281
|
+
rel: "noopener noreferrer",
|
|
282
|
+
title: "Open the CI workflow that produced this run"
|
|
283
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
284
|
+
name: "external-link",
|
|
285
|
+
size: 14
|
|
286
|
+
}), "Re-run failed"));
|
|
287
|
+
}
|
|
288
|
+
function StatusDonut({
|
|
289
|
+
counts
|
|
290
|
+
}) {
|
|
291
|
+
const total = counts.passed + counts.failed + counts.broken + counts.skipped;
|
|
292
|
+
const pct = Math.round(counts.passed / total * 100);
|
|
293
|
+
const C = 2 * Math.PI * 42;
|
|
294
|
+
const segs = [[counts.passed, 'var(--status-passed)'], [counts.failed, 'var(--status-failed)'], [counts.broken, 'var(--status-broken)'], [counts.skipped, 'var(--status-skipped)']];
|
|
295
|
+
let off = 0;
|
|
296
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
297
|
+
style: {
|
|
298
|
+
display: 'flex',
|
|
299
|
+
alignItems: 'center',
|
|
300
|
+
gap: 18
|
|
301
|
+
}
|
|
302
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
303
|
+
style: {
|
|
304
|
+
position: 'relative',
|
|
305
|
+
width: 150,
|
|
306
|
+
height: 150
|
|
307
|
+
}
|
|
308
|
+
}, /*#__PURE__*/React.createElement("svg", {
|
|
309
|
+
viewBox: "0 0 100 100",
|
|
310
|
+
width: "150",
|
|
311
|
+
height: "150",
|
|
312
|
+
style: {
|
|
313
|
+
transform: 'rotate(-90deg)'
|
|
314
|
+
}
|
|
315
|
+
}, /*#__PURE__*/React.createElement("circle", {
|
|
316
|
+
cx: "50",
|
|
317
|
+
cy: "50",
|
|
318
|
+
r: "42",
|
|
319
|
+
stroke: "var(--bg-sunken)",
|
|
320
|
+
strokeWidth: "14",
|
|
321
|
+
fill: "none"
|
|
322
|
+
}), segs.map(([n, color], i) => {
|
|
323
|
+
const len = n / total * C;
|
|
324
|
+
const dash = `${len} ${C}`;
|
|
325
|
+
const dashoff = -off;
|
|
326
|
+
off += len;
|
|
327
|
+
return n > 0 ? /*#__PURE__*/React.createElement("circle", {
|
|
328
|
+
key: i,
|
|
329
|
+
cx: "50",
|
|
330
|
+
cy: "50",
|
|
331
|
+
r: "42",
|
|
332
|
+
stroke: color,
|
|
333
|
+
strokeWidth: "14",
|
|
334
|
+
fill: "none",
|
|
335
|
+
strokeDasharray: dash,
|
|
336
|
+
strokeDashoffset: dashoff
|
|
337
|
+
}) : null;
|
|
338
|
+
})), /*#__PURE__*/React.createElement("div", {
|
|
339
|
+
style: {
|
|
340
|
+
position: 'absolute',
|
|
341
|
+
inset: 0,
|
|
342
|
+
display: 'flex',
|
|
343
|
+
flexDirection: 'column',
|
|
344
|
+
alignItems: 'center',
|
|
345
|
+
justifyContent: 'center'
|
|
346
|
+
}
|
|
347
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
348
|
+
style: {
|
|
349
|
+
fontFamily: 'var(--font-display)',
|
|
350
|
+
fontSize: 36,
|
|
351
|
+
fontWeight: 800,
|
|
352
|
+
letterSpacing: '-0.025em',
|
|
353
|
+
color: 'var(--fg1)'
|
|
354
|
+
}
|
|
355
|
+
}, pct, "%"), /*#__PURE__*/React.createElement("div", {
|
|
356
|
+
style: {
|
|
357
|
+
fontSize: 10,
|
|
358
|
+
letterSpacing: '0.12em',
|
|
359
|
+
textTransform: 'uppercase',
|
|
360
|
+
color: 'var(--fg3)',
|
|
361
|
+
fontWeight: 600
|
|
362
|
+
}
|
|
363
|
+
}, "passed"))), /*#__PURE__*/React.createElement("div", {
|
|
364
|
+
style: {
|
|
365
|
+
display: 'flex',
|
|
366
|
+
flexDirection: 'column',
|
|
367
|
+
gap: 8
|
|
368
|
+
}
|
|
369
|
+
}, [['passed', counts.passed, 'var(--status-passed)'], ['failed', counts.failed, 'var(--status-failed)'], ['broken', counts.broken, 'var(--status-broken)'], ['skipped', counts.skipped, 'var(--status-skipped)']].map(([k, v, c]) => /*#__PURE__*/React.createElement("div", {
|
|
370
|
+
key: k,
|
|
371
|
+
style: {
|
|
372
|
+
display: 'flex',
|
|
373
|
+
alignItems: 'center',
|
|
374
|
+
gap: 8,
|
|
375
|
+
fontFamily: 'var(--font-mono)',
|
|
376
|
+
fontSize: 12,
|
|
377
|
+
color: 'var(--fg2)'
|
|
378
|
+
}
|
|
379
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
380
|
+
style: {
|
|
381
|
+
width: 10,
|
|
382
|
+
height: 10,
|
|
383
|
+
borderRadius: 2,
|
|
384
|
+
background: c
|
|
385
|
+
}
|
|
386
|
+
}), /*#__PURE__*/React.createElement("b", {
|
|
387
|
+
style: {
|
|
388
|
+
color: 'var(--fg1)',
|
|
389
|
+
fontFamily: 'var(--font-display)',
|
|
390
|
+
fontWeight: 700,
|
|
391
|
+
marginRight: 4,
|
|
392
|
+
fontVariantNumeric: 'tabular-nums'
|
|
393
|
+
}
|
|
394
|
+
}, v), " ", k))));
|
|
395
|
+
}
|
|
396
|
+
function TrendChart() {
|
|
397
|
+
return /*#__PURE__*/React.createElement("svg", {
|
|
398
|
+
viewBox: "0 0 400 160",
|
|
399
|
+
preserveAspectRatio: "none",
|
|
400
|
+
style: {
|
|
401
|
+
display: 'block',
|
|
402
|
+
width: '100%',
|
|
403
|
+
height: 160
|
|
404
|
+
}
|
|
405
|
+
}, [1, 2, 3, 4, 5, 6, 7].map(i => /*#__PURE__*/React.createElement("line", {
|
|
406
|
+
key: i,
|
|
407
|
+
x1: i * 50,
|
|
408
|
+
y1: "10",
|
|
409
|
+
x2: i * 50,
|
|
410
|
+
y2: "150",
|
|
411
|
+
stroke: "var(--line)",
|
|
412
|
+
strokeDasharray: "2 3"
|
|
413
|
+
})), /*#__PURE__*/React.createElement("path", {
|
|
414
|
+
d: "M0,140 L50,90 L100,30 L150,30 L200,30 L250,30 L300,30 L350,55 L400,60 L400,150 L0,150 Z",
|
|
415
|
+
fill: "var(--status-passed)",
|
|
416
|
+
fillOpacity: "0.7"
|
|
417
|
+
}), /*#__PURE__*/React.createElement("path", {
|
|
418
|
+
d: "M0,150 L50,140 L100,110 L150,110 L200,105 L250,105 L300,105 L350,100 L400,98 L400,150 L0,150 Z",
|
|
419
|
+
fill: "var(--status-failed)",
|
|
420
|
+
fillOpacity: "0.75"
|
|
421
|
+
}), [0, 50, 100, 150, 200, 250, 300, 350].map((x, i) => /*#__PURE__*/React.createElement("text", {
|
|
422
|
+
key: i,
|
|
423
|
+
x: x + 25,
|
|
424
|
+
y: "158",
|
|
425
|
+
textAnchor: "middle",
|
|
426
|
+
fontFamily: "var(--font-mono)",
|
|
427
|
+
fontSize: "9",
|
|
428
|
+
fill: "var(--fg3)"
|
|
429
|
+
}, "#", 2455086 + i)));
|
|
430
|
+
}
|
|
431
|
+
function SuiteBar({
|
|
432
|
+
name,
|
|
433
|
+
segs,
|
|
434
|
+
total
|
|
435
|
+
}) {
|
|
436
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
437
|
+
style: {
|
|
438
|
+
display: 'grid',
|
|
439
|
+
gridTemplateColumns: '260px 1fr 50px',
|
|
440
|
+
alignItems: 'center',
|
|
441
|
+
gap: 14,
|
|
442
|
+
padding: '6px 0'
|
|
443
|
+
}
|
|
444
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
445
|
+
style: {
|
|
446
|
+
fontFamily: 'var(--font-mono)',
|
|
447
|
+
fontSize: 12.5,
|
|
448
|
+
color: 'var(--fg1)'
|
|
449
|
+
}
|
|
450
|
+
}, name), /*#__PURE__*/React.createElement("div", {
|
|
451
|
+
style: {
|
|
452
|
+
height: 18,
|
|
453
|
+
background: 'var(--bg-sunken)',
|
|
454
|
+
borderRadius: 4,
|
|
455
|
+
overflow: 'hidden',
|
|
456
|
+
display: 'flex'
|
|
457
|
+
}
|
|
458
|
+
}, segs.map((s, i) => /*#__PURE__*/React.createElement("div", {
|
|
459
|
+
key: i,
|
|
460
|
+
style: {
|
|
461
|
+
width: `${s.n / total * 100}%`,
|
|
462
|
+
background: `var(--status-${s.k})`,
|
|
463
|
+
color: '#fff',
|
|
464
|
+
fontSize: 11,
|
|
465
|
+
fontWeight: 600,
|
|
466
|
+
display: 'flex',
|
|
467
|
+
alignItems: 'center',
|
|
468
|
+
justifyContent: 'flex-end',
|
|
469
|
+
paddingRight: 6
|
|
470
|
+
}
|
|
471
|
+
}, s.n))), /*#__PURE__*/React.createElement("div", {
|
|
472
|
+
style: {
|
|
473
|
+
fontFamily: 'var(--font-mono)',
|
|
474
|
+
fontSize: 11,
|
|
475
|
+
color: 'var(--fg3)',
|
|
476
|
+
textAlign: 'right'
|
|
477
|
+
}
|
|
478
|
+
}, total));
|
|
479
|
+
}
|
|
480
|
+
function TestRow({
|
|
481
|
+
test,
|
|
482
|
+
onOpen
|
|
483
|
+
}) {
|
|
484
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
485
|
+
className: "trow",
|
|
486
|
+
onClick: () => onOpen(test)
|
|
487
|
+
}, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("span", {
|
|
488
|
+
className: `s-icon ${test.status}`
|
|
489
|
+
}, test.status === 'passed' ? '✓' : test.status === 'failed' ? '✕' : test.status === 'broken' ? '!' : '⊘')), /*#__PURE__*/React.createElement("div", {
|
|
490
|
+
className: "id"
|
|
491
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
492
|
+
className: "ns"
|
|
493
|
+
}, test.ns), test.name, test.retries ? /*#__PURE__*/React.createElement("span", {
|
|
494
|
+
style: {
|
|
495
|
+
marginLeft: 8,
|
|
496
|
+
fontFamily: 'var(--font-mono)',
|
|
497
|
+
fontSize: 11,
|
|
498
|
+
color: 'var(--status-broken)'
|
|
499
|
+
}
|
|
500
|
+
}, "\u21BB ", test.retries, " retries") : null), /*#__PURE__*/React.createElement("div", {
|
|
501
|
+
className: "dur"
|
|
502
|
+
}, test.duration), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("span", {
|
|
503
|
+
className: `badge b-${test.status}`
|
|
504
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
505
|
+
className: "dot"
|
|
506
|
+
}), test.status)), /*#__PURE__*/React.createElement("div", {
|
|
507
|
+
className: "dur"
|
|
508
|
+
}, test.last), /*#__PURE__*/React.createElement("div", {
|
|
509
|
+
style: {
|
|
510
|
+
color: 'var(--fg4)'
|
|
511
|
+
}
|
|
512
|
+
}, "\u203A"));
|
|
513
|
+
}
|
|
514
|
+
function EnvTable({
|
|
515
|
+
env
|
|
516
|
+
}) {
|
|
517
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
518
|
+
className: "env"
|
|
519
|
+
}, env.map(([k, v]) => /*#__PURE__*/React.createElement(React.Fragment, {
|
|
520
|
+
key: k
|
|
521
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
522
|
+
className: "k"
|
|
523
|
+
}, k), /*#__PURE__*/React.createElement("div", {
|
|
524
|
+
className: "v"
|
|
525
|
+
}, v))));
|
|
526
|
+
}
|
|
527
|
+
function StepTree({
|
|
528
|
+
steps
|
|
529
|
+
}) {
|
|
530
|
+
return /*#__PURE__*/React.createElement("div", null, steps.map((s, i) => /*#__PURE__*/React.createElement("div", {
|
|
531
|
+
key: i,
|
|
532
|
+
className: `step ${s.status}`
|
|
533
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
534
|
+
className: "head"
|
|
535
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
536
|
+
className: `s-icon ${s.status}`,
|
|
537
|
+
style: {
|
|
538
|
+
width: 14,
|
|
539
|
+
height: 14
|
|
540
|
+
}
|
|
541
|
+
}, s.status === 'passed' ? '✓' : s.status === 'failed' ? '✕' : '!'), /*#__PURE__*/React.createElement("span", {
|
|
542
|
+
className: "name"
|
|
543
|
+
}, s.name), /*#__PURE__*/React.createElement("span", {
|
|
544
|
+
className: "dur"
|
|
545
|
+
}, s.duration)), s.body && /*#__PURE__*/React.createElement("div", {
|
|
546
|
+
className: "body"
|
|
547
|
+
}, s.body), s.children && /*#__PURE__*/React.createElement("div", {
|
|
548
|
+
className: "children"
|
|
549
|
+
}, /*#__PURE__*/React.createElement(StepTree, {
|
|
550
|
+
steps: s.children
|
|
551
|
+
})))));
|
|
552
|
+
}
|
|
553
|
+
function LogPanel({
|
|
554
|
+
lines
|
|
555
|
+
}) {
|
|
556
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
557
|
+
className: "log"
|
|
558
|
+
}, lines.map((l, i) => /*#__PURE__*/React.createElement("div", {
|
|
559
|
+
key: i
|
|
560
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
561
|
+
className: "ts"
|
|
562
|
+
}, l.ts), /*#__PURE__*/React.createElement("span", {
|
|
563
|
+
className: `lvl-${l.lvl}`
|
|
564
|
+
}, l.lvl.toUpperCase()), ' ', /*#__PURE__*/React.createElement("span", null, l.msg))));
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// =============================================================
|
|
568
|
+
// SeverityDistribution — horizontal bar viz of test cases by
|
|
569
|
+
// severity. Each row: severity label · stretched bar · count.
|
|
570
|
+
// Blocker/critical use failure tones, Normal uses warning amber,
|
|
571
|
+
// Minor/trivial use muted gray. Rows with zero are hidden.
|
|
572
|
+
// =============================================================
|
|
573
|
+
function SeverityDistribution({
|
|
574
|
+
tests
|
|
575
|
+
}) {
|
|
576
|
+
const ROWS = [{
|
|
577
|
+
k: 'blocker',
|
|
578
|
+
label: 'Blocker',
|
|
579
|
+
fg: 'var(--status-failed)',
|
|
580
|
+
bg: 'var(--status-failed)'
|
|
581
|
+
}, {
|
|
582
|
+
k: 'critical',
|
|
583
|
+
label: 'Critical',
|
|
584
|
+
fg: 'var(--status-failed-fg)',
|
|
585
|
+
bg: '#E5848A'
|
|
586
|
+
}, {
|
|
587
|
+
k: 'normal',
|
|
588
|
+
label: 'Normal',
|
|
589
|
+
fg: 'var(--status-broken-fg)',
|
|
590
|
+
bg: 'var(--status-broken)'
|
|
591
|
+
}, {
|
|
592
|
+
k: 'minor',
|
|
593
|
+
label: 'Minor',
|
|
594
|
+
fg: 'var(--fg2)',
|
|
595
|
+
bg: 'var(--fg3)'
|
|
596
|
+
}, {
|
|
597
|
+
k: 'trivial',
|
|
598
|
+
label: 'Trivial',
|
|
599
|
+
fg: 'var(--fg3)',
|
|
600
|
+
bg: 'var(--fg4)'
|
|
601
|
+
}];
|
|
602
|
+
const counts = {
|
|
603
|
+
blocker: 0,
|
|
604
|
+
critical: 0,
|
|
605
|
+
normal: 0,
|
|
606
|
+
minor: 0,
|
|
607
|
+
trivial: 0
|
|
608
|
+
};
|
|
609
|
+
for (const t of tests) {
|
|
610
|
+
const sev = (t.severity || 'normal').toLowerCase();
|
|
611
|
+
if (counts[sev] != null) counts[sev]++;else counts.normal++;
|
|
612
|
+
}
|
|
613
|
+
const max = Math.max(1, ...Object.values(counts));
|
|
614
|
+
const visible = ROWS.filter(r => counts[r.k] > 0);
|
|
615
|
+
if (visible.length === 0) {
|
|
616
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
617
|
+
style: {
|
|
618
|
+
padding: '14px 0',
|
|
619
|
+
color: 'var(--fg3)',
|
|
620
|
+
fontFamily: 'var(--font-mono)',
|
|
621
|
+
fontSize: 12
|
|
622
|
+
}
|
|
623
|
+
}, "No severity metadata on tests.");
|
|
624
|
+
}
|
|
625
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
626
|
+
style: {
|
|
627
|
+
display: 'flex',
|
|
628
|
+
flexDirection: 'column',
|
|
629
|
+
gap: 10
|
|
630
|
+
}
|
|
631
|
+
}, visible.map(r => {
|
|
632
|
+
const n = counts[r.k];
|
|
633
|
+
const w = n / max * 100;
|
|
634
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
635
|
+
key: r.k,
|
|
636
|
+
style: {
|
|
637
|
+
display: 'grid',
|
|
638
|
+
gridTemplateColumns: '80px 1fr 40px',
|
|
639
|
+
gap: 14,
|
|
640
|
+
alignItems: 'center'
|
|
641
|
+
}
|
|
642
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
643
|
+
style: {
|
|
644
|
+
fontFamily: 'var(--font-body)',
|
|
645
|
+
fontSize: 13,
|
|
646
|
+
fontWeight: 600,
|
|
647
|
+
color: r.fg
|
|
648
|
+
}
|
|
649
|
+
}, r.label), /*#__PURE__*/React.createElement("div", {
|
|
650
|
+
style: {
|
|
651
|
+
position: 'relative',
|
|
652
|
+
height: 14,
|
|
653
|
+
background: 'var(--bg-sunken)',
|
|
654
|
+
borderRadius: 999,
|
|
655
|
+
overflow: 'hidden'
|
|
656
|
+
}
|
|
657
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
658
|
+
style: {
|
|
659
|
+
position: 'absolute',
|
|
660
|
+
inset: 0,
|
|
661
|
+
width: `${w}%`,
|
|
662
|
+
background: r.bg,
|
|
663
|
+
borderRadius: 999,
|
|
664
|
+
transition: 'width var(--dur-fast)'
|
|
665
|
+
}
|
|
666
|
+
})), /*#__PURE__*/React.createElement("div", {
|
|
667
|
+
style: {
|
|
668
|
+
fontFamily: 'var(--font-mono)',
|
|
669
|
+
fontSize: 13,
|
|
670
|
+
fontWeight: 700,
|
|
671
|
+
color: 'var(--fg1)',
|
|
672
|
+
textAlign: 'right',
|
|
673
|
+
fontVariantNumeric: 'tabular-nums'
|
|
674
|
+
}
|
|
675
|
+
}, n));
|
|
676
|
+
}));
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// =============================================================
|
|
680
|
+
// SlowestTestsList — top N tests by duration. Each row mirrors
|
|
681
|
+
// the design from the Allure / TestRail screenshots: status pill
|
|
682
|
+
// on the left, test name + suite path centered, duration right.
|
|
683
|
+
// Click row → opens the test detail.
|
|
684
|
+
// =============================================================
|
|
685
|
+
function SlowestTestsList({
|
|
686
|
+
tests,
|
|
687
|
+
limit = 6,
|
|
688
|
+
onOpen
|
|
689
|
+
}) {
|
|
690
|
+
const ranked = [...tests].filter(t => t.durMs > 0).sort((a, b) => b.durMs - a.durMs).slice(0, limit);
|
|
691
|
+
if (ranked.length === 0) {
|
|
692
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
693
|
+
style: {
|
|
694
|
+
padding: '14px 0',
|
|
695
|
+
color: 'var(--fg3)',
|
|
696
|
+
fontFamily: 'var(--font-mono)',
|
|
697
|
+
fontSize: 12
|
|
698
|
+
}
|
|
699
|
+
}, "No timing data captured for this run.");
|
|
700
|
+
}
|
|
701
|
+
const PILL = {
|
|
702
|
+
passed: {
|
|
703
|
+
bg: 'var(--status-passed-bg)',
|
|
704
|
+
fg: 'var(--status-passed)',
|
|
705
|
+
label: 'PASS'
|
|
706
|
+
},
|
|
707
|
+
failed: {
|
|
708
|
+
bg: 'var(--status-failed-bg)',
|
|
709
|
+
fg: 'var(--status-failed)',
|
|
710
|
+
label: 'FAIL'
|
|
711
|
+
},
|
|
712
|
+
broken: {
|
|
713
|
+
bg: 'var(--status-broken-bg)',
|
|
714
|
+
fg: 'var(--status-broken)',
|
|
715
|
+
label: 'BROKEN'
|
|
716
|
+
},
|
|
717
|
+
skipped: {
|
|
718
|
+
bg: 'var(--status-skipped-bg)',
|
|
719
|
+
fg: 'var(--status-skipped-fg)',
|
|
720
|
+
label: 'SKIP'
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
724
|
+
style: {
|
|
725
|
+
display: 'flex',
|
|
726
|
+
flexDirection: 'column'
|
|
727
|
+
}
|
|
728
|
+
}, ranked.map((t, i) => {
|
|
729
|
+
const p = PILL[t.status] || PILL.passed;
|
|
730
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
731
|
+
key: t.id,
|
|
732
|
+
onClick: () => onOpen?.({
|
|
733
|
+
ns: '',
|
|
734
|
+
name: t.name,
|
|
735
|
+
status: t.status,
|
|
736
|
+
duration: t.dur,
|
|
737
|
+
retries: t.retries,
|
|
738
|
+
richId: t.id
|
|
739
|
+
}),
|
|
740
|
+
style: {
|
|
741
|
+
display: 'grid',
|
|
742
|
+
gridTemplateColumns: '62px 1fr auto',
|
|
743
|
+
alignItems: 'center',
|
|
744
|
+
gap: 14,
|
|
745
|
+
padding: '10px 4px',
|
|
746
|
+
cursor: 'pointer',
|
|
747
|
+
borderTop: i ? '1px solid var(--line)' : 'none',
|
|
748
|
+
transition: 'background var(--dur-fast)'
|
|
749
|
+
},
|
|
750
|
+
onMouseEnter: e => e.currentTarget.style.background = 'var(--bg-hover)',
|
|
751
|
+
onMouseLeave: e => e.currentTarget.style.background = 'transparent'
|
|
752
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
753
|
+
style: {
|
|
754
|
+
display: 'inline-flex',
|
|
755
|
+
alignItems: 'center',
|
|
756
|
+
justifyContent: 'center',
|
|
757
|
+
padding: '3px 0',
|
|
758
|
+
background: p.bg,
|
|
759
|
+
color: p.fg,
|
|
760
|
+
fontFamily: 'var(--font-mono)',
|
|
761
|
+
fontSize: 10.5,
|
|
762
|
+
fontWeight: 700,
|
|
763
|
+
letterSpacing: 0.5,
|
|
764
|
+
borderRadius: 4
|
|
765
|
+
}
|
|
766
|
+
}, p.label), /*#__PURE__*/React.createElement("div", {
|
|
767
|
+
style: {
|
|
768
|
+
minWidth: 0
|
|
769
|
+
}
|
|
770
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
771
|
+
style: {
|
|
772
|
+
fontFamily: 'var(--font-body)',
|
|
773
|
+
fontSize: 13,
|
|
774
|
+
color: 'var(--fg1)',
|
|
775
|
+
fontWeight: 500,
|
|
776
|
+
overflow: 'hidden',
|
|
777
|
+
textOverflow: 'ellipsis',
|
|
778
|
+
whiteSpace: 'nowrap'
|
|
779
|
+
}
|
|
780
|
+
}, t.suite ? /*#__PURE__*/React.createElement("span", {
|
|
781
|
+
style: {
|
|
782
|
+
color: 'var(--fg3)'
|
|
783
|
+
}
|
|
784
|
+
}, t.suite, " \u203A ") : null, t.name), t.file && /*#__PURE__*/React.createElement("div", {
|
|
785
|
+
style: {
|
|
786
|
+
fontFamily: 'var(--font-mono)',
|
|
787
|
+
fontSize: 11,
|
|
788
|
+
color: 'var(--fg3)',
|
|
789
|
+
marginTop: 2,
|
|
790
|
+
overflow: 'hidden',
|
|
791
|
+
textOverflow: 'ellipsis',
|
|
792
|
+
whiteSpace: 'nowrap'
|
|
793
|
+
}
|
|
794
|
+
}, t.file)), /*#__PURE__*/React.createElement("div", {
|
|
795
|
+
style: {
|
|
796
|
+
fontFamily: 'var(--font-mono)',
|
|
797
|
+
fontSize: 13,
|
|
798
|
+
fontWeight: 700,
|
|
799
|
+
color: 'var(--fg1)',
|
|
800
|
+
fontVariantNumeric: 'tabular-nums'
|
|
801
|
+
}
|
|
802
|
+
}, t.dur));
|
|
803
|
+
}));
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// =============================================================
|
|
807
|
+
// HighlightStat — accent-bordered "hero" stat card. Used on the
|
|
808
|
+
// Graphs page banner: Slowest test · Most retried · Top failure
|
|
809
|
+
// category. Big numeric, subtle subtitle, color-coded left border.
|
|
810
|
+
// =============================================================
|
|
811
|
+
function HighlightStat({
|
|
812
|
+
overline,
|
|
813
|
+
value,
|
|
814
|
+
valueColor,
|
|
815
|
+
subtitle,
|
|
816
|
+
accent,
|
|
817
|
+
onClick,
|
|
818
|
+
title
|
|
819
|
+
}) {
|
|
820
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
821
|
+
onClick: onClick,
|
|
822
|
+
title: title,
|
|
823
|
+
style: {
|
|
824
|
+
position: 'relative',
|
|
825
|
+
padding: '18px 22px',
|
|
826
|
+
background: 'var(--bg-elev)',
|
|
827
|
+
border: '1px solid var(--line)',
|
|
828
|
+
borderRadius: 12,
|
|
829
|
+
cursor: onClick ? 'pointer' : 'default',
|
|
830
|
+
overflow: 'hidden',
|
|
831
|
+
transition: 'transform var(--dur-fast), border-color var(--dur-fast)'
|
|
832
|
+
},
|
|
833
|
+
onMouseEnter: onClick ? e => {
|
|
834
|
+
e.currentTarget.style.borderColor = accent;
|
|
835
|
+
e.currentTarget.style.transform = 'translateY(-1px)';
|
|
836
|
+
} : undefined,
|
|
837
|
+
onMouseLeave: onClick ? e => {
|
|
838
|
+
e.currentTarget.style.borderColor = 'var(--line)';
|
|
839
|
+
e.currentTarget.style.transform = 'translateY(0)';
|
|
840
|
+
} : undefined
|
|
841
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
842
|
+
style: {
|
|
843
|
+
position: 'absolute',
|
|
844
|
+
left: 0,
|
|
845
|
+
top: 0,
|
|
846
|
+
bottom: 0,
|
|
847
|
+
width: 4,
|
|
848
|
+
background: accent
|
|
849
|
+
}
|
|
850
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
851
|
+
style: {
|
|
852
|
+
fontFamily: 'var(--font-mono)',
|
|
853
|
+
fontSize: 10.5,
|
|
854
|
+
color: 'var(--fg3)',
|
|
855
|
+
letterSpacing: '.14em',
|
|
856
|
+
textTransform: 'uppercase',
|
|
857
|
+
marginBottom: 8
|
|
858
|
+
}
|
|
859
|
+
}, overline), /*#__PURE__*/React.createElement("div", {
|
|
860
|
+
style: {
|
|
861
|
+
fontFamily: 'var(--font-display)',
|
|
862
|
+
fontSize: 38,
|
|
863
|
+
fontWeight: 700,
|
|
864
|
+
letterSpacing: -0.8,
|
|
865
|
+
color: valueColor || 'var(--fg1)',
|
|
866
|
+
lineHeight: 1,
|
|
867
|
+
marginBottom: 10
|
|
868
|
+
}
|
|
869
|
+
}, value), /*#__PURE__*/React.createElement("div", {
|
|
870
|
+
style: {
|
|
871
|
+
fontFamily: 'var(--font-body)',
|
|
872
|
+
fontSize: 13,
|
|
873
|
+
color: 'var(--fg2)',
|
|
874
|
+
overflow: 'hidden',
|
|
875
|
+
textOverflow: 'ellipsis',
|
|
876
|
+
whiteSpace: 'nowrap'
|
|
877
|
+
}
|
|
878
|
+
}, subtitle));
|
|
879
|
+
}
|
|
880
|
+
Object.assign(window, {
|
|
881
|
+
Sidebar,
|
|
882
|
+
TopBar,
|
|
883
|
+
StatusDonut,
|
|
884
|
+
TrendChart,
|
|
885
|
+
SuiteBar,
|
|
886
|
+
TestRow,
|
|
887
|
+
EnvTable,
|
|
888
|
+
StepTree,
|
|
889
|
+
LogPanel,
|
|
890
|
+
Icon,
|
|
891
|
+
SeverityDistribution,
|
|
892
|
+
SlowestTestsList,
|
|
893
|
+
HighlightStat
|
|
894
|
+
});
|