executable-stories-formatters 0.2.0 → 0.4.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/README.md +1 -1
- package/dist/adapters.d.cts +1 -1
- package/dist/adapters.d.ts +1 -1
- package/dist/cli.js +401 -7
- package/dist/cli.js.map +1 -1
- package/dist/{index-DCJ0NvAp.d.cts → index-DyeUWfYK.d.cts} +26 -1
- package/dist/{index-DCJ0NvAp.d.ts → index-DyeUWfYK.d.ts} +26 -1
- package/dist/index.cjs +433 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -3
- package/dist/index.d.ts +29 -3
- package/dist/index.js +430 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schemas/README.md +7 -12
package/README.md
CHANGED
|
@@ -119,7 +119,7 @@ const generator = new ReportGenerator({
|
|
|
119
119
|
|
|
120
120
|
| Format | Description | File Extension |
|
|
121
121
|
| --- | --- | --- |
|
|
122
|
-
| `html` | Interactive HTML report with search and
|
|
122
|
+
| `html` | Interactive HTML report with search, screenshots, step parameter highlighting (quoted strings and numbers), syntax-highlighted code blocks, Mermaid diagrams, and Markdown in doc sections | `.html` |
|
|
123
123
|
| `markdown` | Markdown user-story documentation | `.md` |
|
|
124
124
|
| `junit` | JUnit XML for CI integration | `.junit.xml` |
|
|
125
125
|
| `cucumber-json` | Cucumber JSON for tooling compatibility | `.cucumber.json` |
|
package/dist/adapters.d.cts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { J as JestAdapterOptions, i as JestAggregatedResult, j as JestFileResult, k as JestTestResult, P as PlaywrightAdapterOptions,
|
|
1
|
+
export { J as JestAdapterOptions, i as JestAggregatedResult, j as JestFileResult, k as JestTestResult, P as PlaywrightAdapterOptions, m as PlaywrightAnnotation, n as PlaywrightAttachment, o as PlaywrightError, p as PlaywrightLocation, q as PlaywrightStatus, r as PlaywrightTestCase, s as PlaywrightTestResult, y as StoryFileReport, V as VitestAdapterOptions, z as VitestSerializedError, A as VitestState, B as VitestTestCase, C as VitestTestModule, E as VitestTestResult, e as adaptJestRun, f as adaptPlaywrightRun, g as adaptVitestRun } from './index-DyeUWfYK.cjs';
|
package/dist/adapters.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { J as JestAdapterOptions, i as JestAggregatedResult, j as JestFileResult, k as JestTestResult, P as PlaywrightAdapterOptions,
|
|
1
|
+
export { J as JestAdapterOptions, i as JestAggregatedResult, j as JestFileResult, k as JestTestResult, P as PlaywrightAdapterOptions, m as PlaywrightAnnotation, n as PlaywrightAttachment, o as PlaywrightError, p as PlaywrightLocation, q as PlaywrightStatus, r as PlaywrightTestCase, s as PlaywrightTestResult, y as StoryFileReport, V as VitestAdapterOptions, z as VitestSerializedError, A as VitestState, B as VitestTestCase, C as VitestTestModule, E as VitestTestResult, e as adaptJestRun, f as adaptPlaywrightRun, g as adaptVitestRun } from './index-DyeUWfYK.js';
|
package/dist/cli.js
CHANGED
|
@@ -1086,20 +1086,32 @@ function initCollapse() {
|
|
|
1086
1086
|
}
|
|
1087
1087
|
});
|
|
1088
1088
|
});
|
|
1089
|
+
|
|
1090
|
+
document.querySelectorAll('.trace-view-header').forEach(header => {
|
|
1091
|
+
header.addEventListener('click', () => {
|
|
1092
|
+
toggleCollapse(header, header.closest('.trace-view'));
|
|
1093
|
+
});
|
|
1094
|
+
header.addEventListener('keydown', (e) => {
|
|
1095
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
1096
|
+
e.preventDefault();
|
|
1097
|
+
toggleCollapse(header, header.closest('.trace-view'));
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
});
|
|
1089
1101
|
}
|
|
1090
1102
|
|
|
1091
1103
|
function expandAll() {
|
|
1092
|
-
document.querySelectorAll('.feature, .scenario').forEach(el => {
|
|
1104
|
+
document.querySelectorAll('.feature, .scenario, .trace-view').forEach(el => {
|
|
1093
1105
|
el.classList.remove('collapsed');
|
|
1094
|
-
const header = el.querySelector('.feature-header, .scenario-header');
|
|
1106
|
+
const header = el.querySelector('.feature-header, .scenario-header, .trace-view-header');
|
|
1095
1107
|
header?.setAttribute('aria-expanded', 'true');
|
|
1096
1108
|
});
|
|
1097
1109
|
}
|
|
1098
1110
|
|
|
1099
1111
|
function collapseAll() {
|
|
1100
|
-
document.querySelectorAll('.feature, .scenario').forEach(el => {
|
|
1112
|
+
document.querySelectorAll('.feature, .scenario, .trace-view').forEach(el => {
|
|
1101
1113
|
el.classList.add('collapsed');
|
|
1102
|
-
const header = el.querySelector('.feature-header, .scenario-header');
|
|
1114
|
+
const header = el.querySelector('.feature-header, .scenario-header, .trace-view-header');
|
|
1103
1115
|
header?.setAttribute('aria-expanded', 'false');
|
|
1104
1116
|
});
|
|
1105
1117
|
}
|
|
@@ -1289,6 +1301,7 @@ var CSS_STYLES = `
|
|
|
1289
1301
|
--tag-bg: hsl(145 55% 95%);
|
|
1290
1302
|
--tag-color: hsl(145 63% 30%);
|
|
1291
1303
|
--tag-border: hsl(145 55% 85%);
|
|
1304
|
+
--step-param-color: hsl(220 70% 50%);
|
|
1292
1305
|
|
|
1293
1306
|
/* Accordion/Collapsible styling */
|
|
1294
1307
|
--accordion-header-hover: hsl(0 0% 98%);
|
|
@@ -1347,6 +1360,7 @@ var CSS_STYLES = `
|
|
|
1347
1360
|
--tag-bg: hsl(145 35% 14%);
|
|
1348
1361
|
--tag-color: hsl(145 63% 60%);
|
|
1349
1362
|
--tag-border: hsl(145 35% 22%);
|
|
1363
|
+
--step-param-color: hsl(220 70% 70%);
|
|
1350
1364
|
|
|
1351
1365
|
/* Accordion/Collapsible styling */
|
|
1352
1366
|
--accordion-header-hover: hsl(0 0% 11%);
|
|
@@ -1395,6 +1409,7 @@ var CSS_STYLES = `
|
|
|
1395
1409
|
--tag-bg: hsl(145 35% 14%);
|
|
1396
1410
|
--tag-color: hsl(145 63% 60%);
|
|
1397
1411
|
--tag-border: hsl(145 35% 22%);
|
|
1412
|
+
--step-param-color: hsl(220 70% 70%);
|
|
1398
1413
|
--accordion-header-hover: hsl(0 0% 11%);
|
|
1399
1414
|
--accordion-content-bg: hsl(0 0% 7%);
|
|
1400
1415
|
}
|
|
@@ -1940,6 +1955,12 @@ body {
|
|
|
1940
1955
|
color: var(--foreground);
|
|
1941
1956
|
}
|
|
1942
1957
|
|
|
1958
|
+
.step-param {
|
|
1959
|
+
font-style: italic;
|
|
1960
|
+
font-weight: 500;
|
|
1961
|
+
color: var(--step-param-color);
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1943
1964
|
.step-duration {
|
|
1944
1965
|
color: var(--muted-foreground);
|
|
1945
1966
|
font-size: 0.6875rem;
|
|
@@ -2616,6 +2637,133 @@ body {
|
|
|
2616
2637
|
font-family: inherit;
|
|
2617
2638
|
background: none;
|
|
2618
2639
|
}
|
|
2640
|
+
|
|
2641
|
+
/* ============================================================================
|
|
2642
|
+
Trace View - OTel span waterfall
|
|
2643
|
+
============================================================================ */
|
|
2644
|
+
.trace-view {
|
|
2645
|
+
margin-top: 0.75rem;
|
|
2646
|
+
border: 1px solid var(--border);
|
|
2647
|
+
border-radius: calc(var(--radius) - 2px);
|
|
2648
|
+
overflow: hidden;
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
.trace-view-header {
|
|
2652
|
+
display: flex;
|
|
2653
|
+
align-items: center;
|
|
2654
|
+
gap: 0.5rem;
|
|
2655
|
+
padding: 0.5rem 0.75rem;
|
|
2656
|
+
background: var(--card);
|
|
2657
|
+
cursor: pointer;
|
|
2658
|
+
user-select: none;
|
|
2659
|
+
font-size: 0.8125rem;
|
|
2660
|
+
font-weight: 500;
|
|
2661
|
+
color: var(--foreground);
|
|
2662
|
+
transition: background-color 0.15s ease;
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
.trace-view-header:hover {
|
|
2666
|
+
background: var(--accordion-header-hover);
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
.trace-view-count {
|
|
2670
|
+
font-size: 0.6875rem;
|
|
2671
|
+
font-weight: 500;
|
|
2672
|
+
padding: 0.125rem 0.5rem;
|
|
2673
|
+
background: var(--success-light);
|
|
2674
|
+
color: var(--success);
|
|
2675
|
+
border: 1px solid var(--success-border);
|
|
2676
|
+
border-radius: 9999px;
|
|
2677
|
+
font-family: var(--font-mono);
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
.trace-view-content {
|
|
2681
|
+
border-top: 1px solid var(--border);
|
|
2682
|
+
padding: 0.5rem 0.75rem;
|
|
2683
|
+
background: var(--accordion-content-bg);
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
.trace-view.collapsed .trace-view-content {
|
|
2687
|
+
display: none;
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
.trace-view-axis {
|
|
2691
|
+
display: flex;
|
|
2692
|
+
justify-content: space-between;
|
|
2693
|
+
font-size: 0.625rem;
|
|
2694
|
+
font-family: var(--font-mono);
|
|
2695
|
+
color: var(--muted-foreground);
|
|
2696
|
+
padding-bottom: 0.375rem;
|
|
2697
|
+
margin-bottom: 0.375rem;
|
|
2698
|
+
border-bottom: 1px solid var(--border);
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
.trace-view-row {
|
|
2702
|
+
display: flex;
|
|
2703
|
+
align-items: center;
|
|
2704
|
+
gap: 0.5rem;
|
|
2705
|
+
padding: 0.1875rem 0;
|
|
2706
|
+
font-size: 0.75rem;
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
.trace-view-name {
|
|
2710
|
+
width: 35%;
|
|
2711
|
+
flex-shrink: 0;
|
|
2712
|
+
display: flex;
|
|
2713
|
+
align-items: center;
|
|
2714
|
+
gap: 0.375rem;
|
|
2715
|
+
font-family: var(--font-mono);
|
|
2716
|
+
white-space: nowrap;
|
|
2717
|
+
overflow: hidden;
|
|
2718
|
+
text-overflow: ellipsis;
|
|
2719
|
+
color: var(--foreground);
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
.trace-view-status-dot {
|
|
2723
|
+
width: 8px;
|
|
2724
|
+
height: 8px;
|
|
2725
|
+
border-radius: 50%;
|
|
2726
|
+
flex-shrink: 0;
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
.trace-view-status-ok { background: var(--success); }
|
|
2730
|
+
.trace-view-status-error { background: var(--error); }
|
|
2731
|
+
.trace-view-status-unset { background: var(--muted-foreground); }
|
|
2732
|
+
|
|
2733
|
+
.trace-view-bar-container {
|
|
2734
|
+
flex: 1;
|
|
2735
|
+
position: relative;
|
|
2736
|
+
height: 1.25rem;
|
|
2737
|
+
background: var(--muted);
|
|
2738
|
+
border-radius: 2px;
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
.trace-view-bar {
|
|
2742
|
+
position: absolute;
|
|
2743
|
+
top: 0;
|
|
2744
|
+
height: 100%;
|
|
2745
|
+
border-radius: 2px;
|
|
2746
|
+
min-width: 2px;
|
|
2747
|
+
display: flex;
|
|
2748
|
+
align-items: center;
|
|
2749
|
+
padding: 0 0.375rem;
|
|
2750
|
+
font-size: 0.625rem;
|
|
2751
|
+
font-family: var(--font-mono);
|
|
2752
|
+
color: white;
|
|
2753
|
+
white-space: nowrap;
|
|
2754
|
+
overflow: hidden;
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
.trace-view-bar-ok { background: var(--success); }
|
|
2758
|
+
.trace-view-bar-error { background: var(--error); }
|
|
2759
|
+
.trace-view-bar-unset { background: var(--muted-foreground); }
|
|
2760
|
+
|
|
2761
|
+
@media print {
|
|
2762
|
+
.trace-view.collapsed .trace-view-content {
|
|
2763
|
+
display: block;
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2619
2767
|
`;
|
|
2620
2768
|
|
|
2621
2769
|
// src/formatters/html/renderers/status.ts
|
|
@@ -2860,10 +3008,11 @@ function renderStep(step, stepResult, index, deps) {
|
|
|
2860
3008
|
const isContinuation = CONTINUATION_KEYWORDS.includes(keywordTrimmed);
|
|
2861
3009
|
const stepClass = isContinuation ? "step continuation" : "step";
|
|
2862
3010
|
const stepDocs = deps.renderDocs(step.docs, "step-docs");
|
|
3011
|
+
const textHtml = deps.highlightStepParams ? deps.highlightStepParams(step.text) : deps.escapeHtml(step.text);
|
|
2863
3012
|
return `<div class="${stepClass}">
|
|
2864
3013
|
<span class="step-status ${statusClass}">${statusIcon}</span>
|
|
2865
3014
|
<span class="step-keyword">${deps.escapeHtml(step.keyword)}</span>
|
|
2866
|
-
<span class="step-text">${
|
|
3015
|
+
<span class="step-text">${textHtml}</span>
|
|
2867
3016
|
<span class="step-duration">${duration}</span>
|
|
2868
3017
|
</div>${stepDocs}`;
|
|
2869
3018
|
}
|
|
@@ -2875,6 +3024,30 @@ function renderSteps(args, deps) {
|
|
|
2875
3024
|
return `<div class="steps">${stepsHtml}</div>`;
|
|
2876
3025
|
}
|
|
2877
3026
|
|
|
3027
|
+
// src/formatters/html/renderers/step-params.ts
|
|
3028
|
+
var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.\-])\d+(?:\.\d+)?(?![\w.\-])/g;
|
|
3029
|
+
function highlightStepParams(text, deps) {
|
|
3030
|
+
const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
|
|
3031
|
+
if (matches.length === 0) {
|
|
3032
|
+
return deps.escapeHtml(text);
|
|
3033
|
+
}
|
|
3034
|
+
let result = "";
|
|
3035
|
+
let lastIndex = 0;
|
|
3036
|
+
for (const match of matches) {
|
|
3037
|
+
const matchStart = match.index;
|
|
3038
|
+
const matchEnd = matchStart + match[0].length;
|
|
3039
|
+
if (matchStart > lastIndex) {
|
|
3040
|
+
result += deps.escapeHtml(text.slice(lastIndex, matchStart));
|
|
3041
|
+
}
|
|
3042
|
+
result += `<span class="step-param">${deps.escapeHtml(match[0])}</span>`;
|
|
3043
|
+
lastIndex = matchEnd;
|
|
3044
|
+
}
|
|
3045
|
+
if (lastIndex < text.length) {
|
|
3046
|
+
result += deps.escapeHtml(text.slice(lastIndex));
|
|
3047
|
+
}
|
|
3048
|
+
return result;
|
|
3049
|
+
}
|
|
3050
|
+
|
|
2878
3051
|
// src/formatters/html/renderers/scenario.ts
|
|
2879
3052
|
function renderScenario(args, deps) {
|
|
2880
3053
|
const { tc } = args;
|
|
@@ -2882,6 +3055,19 @@ function renderScenario(args, deps) {
|
|
|
2882
3055
|
const statusClass = `status-${tc.status}`;
|
|
2883
3056
|
const duration = tc.durationMs > 0 ? `${(tc.durationMs / 1e3).toFixed(2)}s` : "";
|
|
2884
3057
|
const tags = tc.tags.map((t) => `<span class="tag">${deps.escapeHtml(t)}</span>`).join("");
|
|
3058
|
+
const otelMeta = tc.story.meta?.otel;
|
|
3059
|
+
let traceBadge = "";
|
|
3060
|
+
if (otelMeta?.traceId) {
|
|
3061
|
+
const shortId = otelMeta.traceId.slice(0, 16);
|
|
3062
|
+
const traceLink = tc.story.docs?.find(
|
|
3063
|
+
(d) => d.kind === "link" && d.label === "View Trace"
|
|
3064
|
+
);
|
|
3065
|
+
if (traceLink) {
|
|
3066
|
+
traceBadge = `<a class="tag trace-tag" href="${deps.escapeHtml(traceLink.url)}" title="${deps.escapeHtml(otelMeta.traceId)}" target="_blank" rel="noopener">${deps.escapeHtml(shortId)}\u2026</a>`;
|
|
3067
|
+
} else {
|
|
3068
|
+
traceBadge = `<span class="tag trace-tag" title="${deps.escapeHtml(otelMeta.traceId)}">${deps.escapeHtml(shortId)}\u2026</span>`;
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
2885
3071
|
const storyDocs = deps.renderDocs(tc.story.docs, "story-docs");
|
|
2886
3072
|
const steps = deps.renderSteps(
|
|
2887
3073
|
{ steps: tc.story.steps, stepResults: tc.stepResults },
|
|
@@ -2902,6 +3088,10 @@ function renderScenario(args, deps) {
|
|
|
2902
3088
|
embedScreenshots: deps.embedScreenshots
|
|
2903
3089
|
}
|
|
2904
3090
|
);
|
|
3091
|
+
const traceView = deps.renderTraceView(
|
|
3092
|
+
{ spans: tc.story.otelSpans },
|
|
3093
|
+
{ escapeHtml: deps.escapeHtml }
|
|
3094
|
+
);
|
|
2905
3095
|
const collapsedClass = deps.startCollapsed ? " collapsed" : "";
|
|
2906
3096
|
const ariaExpanded = !deps.startCollapsed;
|
|
2907
3097
|
return `
|
|
@@ -2912,7 +3102,7 @@ function renderScenario(args, deps) {
|
|
|
2912
3102
|
<span class="status-icon ${statusClass}">${statusIcon}</span>
|
|
2913
3103
|
<span class="scenario-name">${deps.escapeHtml(tc.story.scenario)}</span>
|
|
2914
3104
|
</div>
|
|
2915
|
-
<div class="scenario-meta">${tags}</div>
|
|
3105
|
+
<div class="scenario-meta">${tags}${traceBadge}</div>
|
|
2916
3106
|
</div>
|
|
2917
3107
|
<span class="scenario-duration">${duration}</span>
|
|
2918
3108
|
</div>
|
|
@@ -2921,6 +3111,193 @@ function renderScenario(args, deps) {
|
|
|
2921
3111
|
${steps}
|
|
2922
3112
|
${error}
|
|
2923
3113
|
${attachments}
|
|
3114
|
+
${traceView}
|
|
3115
|
+
</div>
|
|
3116
|
+
</div>`;
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
// src/formatters/html/renderers/trace-view.ts
|
|
3120
|
+
var VALID_STATUSES = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
|
|
3121
|
+
var TOOLTIP_MAX_LENGTH = 4096;
|
|
3122
|
+
function safeStatus(status) {
|
|
3123
|
+
return VALID_STATUSES.has(status) ? status : "unset";
|
|
3124
|
+
}
|
|
3125
|
+
function formatDuration(ms) {
|
|
3126
|
+
if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
|
|
3127
|
+
return `${ms.toFixed(1)}ms`;
|
|
3128
|
+
}
|
|
3129
|
+
function clamp(value, min, max) {
|
|
3130
|
+
return Math.min(max, Math.max(min, value));
|
|
3131
|
+
}
|
|
3132
|
+
function normalizeSpans(spans) {
|
|
3133
|
+
const result = [];
|
|
3134
|
+
for (const span of spans) {
|
|
3135
|
+
if (!span || typeof span !== "object") continue;
|
|
3136
|
+
if (typeof span.spanId !== "string" || typeof span.name !== "string") continue;
|
|
3137
|
+
let startTimeMs;
|
|
3138
|
+
let durationMs;
|
|
3139
|
+
if (span.startTimeMs != null && span.durationMs != null) {
|
|
3140
|
+
startTimeMs = span.startTimeMs;
|
|
3141
|
+
durationMs = span.durationMs;
|
|
3142
|
+
} else if (span.startTimeUnixNano != null && span.endTimeUnixNano != null) {
|
|
3143
|
+
startTimeMs = span.startTimeUnixNano / 1e6;
|
|
3144
|
+
durationMs = (span.endTimeUnixNano - span.startTimeUnixNano) / 1e6;
|
|
3145
|
+
} else {
|
|
3146
|
+
continue;
|
|
3147
|
+
}
|
|
3148
|
+
durationMs = Math.max(0, durationMs);
|
|
3149
|
+
if (!isFinite(startTimeMs) || !isFinite(durationMs)) continue;
|
|
3150
|
+
result.push({
|
|
3151
|
+
spanId: span.spanId,
|
|
3152
|
+
parentSpanId: span.parentSpanId,
|
|
3153
|
+
name: span.name,
|
|
3154
|
+
startTimeMs,
|
|
3155
|
+
durationMs,
|
|
3156
|
+
status: safeStatus(span.status),
|
|
3157
|
+
statusMessage: span.statusMessage,
|
|
3158
|
+
attributes: span.attributes
|
|
3159
|
+
});
|
|
3160
|
+
}
|
|
3161
|
+
return result;
|
|
3162
|
+
}
|
|
3163
|
+
function buildTree(spans) {
|
|
3164
|
+
const byId = /* @__PURE__ */ new Map();
|
|
3165
|
+
for (const span of spans) {
|
|
3166
|
+
let key = span.spanId;
|
|
3167
|
+
if (byId.has(key)) {
|
|
3168
|
+
let suffix = 2;
|
|
3169
|
+
while (byId.has(`${span.spanId}__dup${suffix}`)) suffix++;
|
|
3170
|
+
key = `${span.spanId}__dup${suffix}`;
|
|
3171
|
+
}
|
|
3172
|
+
byId.set(key, { span: { ...span, spanId: key }, children: [], depth: 0 });
|
|
3173
|
+
}
|
|
3174
|
+
const roots = [];
|
|
3175
|
+
for (const node of byId.values()) {
|
|
3176
|
+
const parentId = node.span.parentSpanId;
|
|
3177
|
+
const parent = parentId ? byId.get(parentId) : void 0;
|
|
3178
|
+
if (parent && parent !== node) {
|
|
3179
|
+
parent.children.push(node);
|
|
3180
|
+
} else {
|
|
3181
|
+
roots.push(node);
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
for (const node of byId.values()) {
|
|
3185
|
+
node.children.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
|
|
3186
|
+
}
|
|
3187
|
+
roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
|
|
3188
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3189
|
+
function assignDepth(node, depth) {
|
|
3190
|
+
if (visited.has(node.span.spanId)) return;
|
|
3191
|
+
visited.add(node.span.spanId);
|
|
3192
|
+
node.depth = depth;
|
|
3193
|
+
for (const child of node.children) {
|
|
3194
|
+
assignDepth(child, depth + 1);
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
for (const root of roots) {
|
|
3198
|
+
assignDepth(root, 0);
|
|
3199
|
+
}
|
|
3200
|
+
for (const node of byId.values()) {
|
|
3201
|
+
if (!visited.has(node.span.spanId)) {
|
|
3202
|
+
node.children = [];
|
|
3203
|
+
roots.push(node);
|
|
3204
|
+
assignDepth(node, 0);
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
|
|
3208
|
+
return roots;
|
|
3209
|
+
}
|
|
3210
|
+
function flattenTree(roots) {
|
|
3211
|
+
const result = [];
|
|
3212
|
+
function walk(node) {
|
|
3213
|
+
result.push(node);
|
|
3214
|
+
for (const child of node.children) {
|
|
3215
|
+
walk(child);
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
for (const root of roots) {
|
|
3219
|
+
walk(root);
|
|
3220
|
+
}
|
|
3221
|
+
return result;
|
|
3222
|
+
}
|
|
3223
|
+
function buildTooltip(span, escapeHtml2) {
|
|
3224
|
+
const parts = [];
|
|
3225
|
+
parts.push(`${span.name} (${formatDuration(span.durationMs)})`);
|
|
3226
|
+
if (span.statusMessage) {
|
|
3227
|
+
parts.push(`Status: ${span.statusMessage}`);
|
|
3228
|
+
}
|
|
3229
|
+
if (span.attributes) {
|
|
3230
|
+
const keys = Object.keys(span.attributes).sort();
|
|
3231
|
+
for (const key of keys) {
|
|
3232
|
+
const val = span.attributes[key];
|
|
3233
|
+
const formatted = Array.isArray(val) ? `[${val.map((v) => String(v)).join(", ")}]` : String(val);
|
|
3234
|
+
parts.push(`${key}=${formatted}`);
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
let text = parts.join("\n");
|
|
3238
|
+
if (text.length > TOOLTIP_MAX_LENGTH) {
|
|
3239
|
+
text = text.slice(0, TOOLTIP_MAX_LENGTH - 3) + "...";
|
|
3240
|
+
}
|
|
3241
|
+
return escapeHtml2(text);
|
|
3242
|
+
}
|
|
3243
|
+
function renderTraceView(args, deps) {
|
|
3244
|
+
if (!args.spans || args.spans.length === 0) return "";
|
|
3245
|
+
const normalized = normalizeSpans(args.spans);
|
|
3246
|
+
if (normalized.length === 0) return "";
|
|
3247
|
+
const roots = buildTree(normalized);
|
|
3248
|
+
const flat = flattenTree(roots);
|
|
3249
|
+
let minStart = Infinity;
|
|
3250
|
+
let maxEnd = -Infinity;
|
|
3251
|
+
for (const node of flat) {
|
|
3252
|
+
const s = node.span.startTimeMs;
|
|
3253
|
+
const e = s + node.span.durationMs;
|
|
3254
|
+
if (s < minStart) minStart = s;
|
|
3255
|
+
if (e > maxEnd) maxEnd = e;
|
|
3256
|
+
}
|
|
3257
|
+
let totalDuration = maxEnd - minStart;
|
|
3258
|
+
if (totalDuration <= 0) totalDuration = 1;
|
|
3259
|
+
const rows = flat.map((node) => {
|
|
3260
|
+
const { span, depth } = node;
|
|
3261
|
+
const indent = depth * 16;
|
|
3262
|
+
const minWidth = 0.5;
|
|
3263
|
+
let spanLeft = clamp(
|
|
3264
|
+
(span.startTimeMs - minStart) / totalDuration * 100,
|
|
3265
|
+
0,
|
|
3266
|
+
100
|
|
3267
|
+
);
|
|
3268
|
+
if (spanLeft + minWidth > 100) {
|
|
3269
|
+
spanLeft = 100 - minWidth;
|
|
3270
|
+
}
|
|
3271
|
+
const spanWidth = clamp(
|
|
3272
|
+
span.durationMs / totalDuration * 100,
|
|
3273
|
+
minWidth,
|
|
3274
|
+
100 - spanLeft
|
|
3275
|
+
);
|
|
3276
|
+
const tooltip = buildTooltip(span, deps.escapeHtml);
|
|
3277
|
+
const durationLabel = formatDuration(span.durationMs);
|
|
3278
|
+
return ` <div class="trace-view-row">
|
|
3279
|
+
<div class="trace-view-name" style="padding-left: ${indent}px" title="${deps.escapeHtml(span.name)}">
|
|
3280
|
+
<span class="trace-view-status-dot trace-view-status-${span.status}"></span>
|
|
3281
|
+
${deps.escapeHtml(span.name)}
|
|
3282
|
+
</div>
|
|
3283
|
+
<div class="trace-view-bar-container">
|
|
3284
|
+
<div class="trace-view-bar trace-view-bar-${span.status}" style="left: ${spanLeft.toFixed(2)}%; width: ${spanWidth.toFixed(2)}%" title="${tooltip}">${durationLabel}</div>
|
|
3285
|
+
</div>
|
|
3286
|
+
</div>`;
|
|
3287
|
+
}).join("\n");
|
|
3288
|
+
const axisEnd = formatDuration(maxEnd - minStart);
|
|
3289
|
+
return `<div class="trace-view collapsed">
|
|
3290
|
+
<div class="trace-view-header" role="button" tabindex="0" aria-expanded="false">
|
|
3291
|
+
<span>Spans</span>
|
|
3292
|
+
<span class="trace-view-count">${flat.length}</span>
|
|
3293
|
+
<span class="chevron">▼</span>
|
|
3294
|
+
</div>
|
|
3295
|
+
<div class="trace-view-content">
|
|
3296
|
+
<div class="trace-view-axis">
|
|
3297
|
+
<span>0ms</span>
|
|
3298
|
+
<span>${axisEnd}</span>
|
|
3299
|
+
</div>
|
|
3300
|
+
${rows}
|
|
2924
3301
|
</div>
|
|
2925
3302
|
</div>`;
|
|
2926
3303
|
}
|
|
@@ -3046,7 +3423,8 @@ function createHtmlFormatter(options = {}) {
|
|
|
3046
3423
|
const stepsDeps = {
|
|
3047
3424
|
escapeHtml,
|
|
3048
3425
|
getStatusIcon,
|
|
3049
|
-
renderDocs
|
|
3426
|
+
renderDocs,
|
|
3427
|
+
highlightStepParams: (text) => highlightStepParams(text, { escapeHtml })
|
|
3050
3428
|
};
|
|
3051
3429
|
const scenarioDeps = {
|
|
3052
3430
|
escapeHtml,
|
|
@@ -3056,6 +3434,7 @@ function createHtmlFormatter(options = {}) {
|
|
|
3056
3434
|
renderDocs,
|
|
3057
3435
|
renderErrorBox: (args, d) => renderErrorBox(args, d),
|
|
3058
3436
|
renderAttachments: (args, d) => renderAttachments(args, d),
|
|
3437
|
+
renderTraceView: (args, d) => renderTraceView(args, d),
|
|
3059
3438
|
embedScreenshots: opts.embedScreenshots
|
|
3060
3439
|
};
|
|
3061
3440
|
const featureDeps = {
|
|
@@ -3348,6 +3727,7 @@ var MarkdownFormatter = class {
|
|
|
3348
3727
|
includeSummaryTable: options.includeSummaryTable ?? false,
|
|
3349
3728
|
permalinkBaseUrl: options.permalinkBaseUrl,
|
|
3350
3729
|
ticketUrlTemplate: options.ticketUrlTemplate,
|
|
3730
|
+
traceUrlTemplate: options.traceUrlTemplate,
|
|
3351
3731
|
includeSourceLinks: options.includeSourceLinks ?? true,
|
|
3352
3732
|
customRenderers: options.customRenderers
|
|
3353
3733
|
};
|
|
@@ -3570,6 +3950,18 @@ var MarkdownFormatter = class {
|
|
|
3570
3950
|
meta.push(`Tickets: ${tc.story.tickets.map((t) => `\`${t}\``).join(", ")}`);
|
|
3571
3951
|
}
|
|
3572
3952
|
}
|
|
3953
|
+
const otelMeta = tc.story.meta?.otel;
|
|
3954
|
+
if (otelMeta?.traceId) {
|
|
3955
|
+
const traceTemplate = this.options.traceUrlTemplate;
|
|
3956
|
+
if (traceTemplate) {
|
|
3957
|
+
const url = traceTemplate.replace(/\{traceId\}/g, otelMeta.traceId);
|
|
3958
|
+
meta.push(
|
|
3959
|
+
`Trace: [${otelMeta.traceId.slice(0, 16)}\u2026](${url})`
|
|
3960
|
+
);
|
|
3961
|
+
} else {
|
|
3962
|
+
meta.push(`Trace: \`${otelMeta.traceId}\``);
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3573
3965
|
if (meta.length > 0) {
|
|
3574
3966
|
lines.push(meta.join(" | "));
|
|
3575
3967
|
}
|
|
@@ -5124,6 +5516,7 @@ var ReportGenerator = class {
|
|
|
5124
5516
|
includeSummaryTable: options.markdown?.includeSummaryTable ?? false,
|
|
5125
5517
|
permalinkBaseUrl: options.markdown?.permalinkBaseUrl,
|
|
5126
5518
|
ticketUrlTemplate: options.markdown?.ticketUrlTemplate,
|
|
5519
|
+
traceUrlTemplate: options.markdown?.traceUrlTemplate,
|
|
5127
5520
|
includeSourceLinks: options.markdown?.includeSourceLinks ?? true,
|
|
5128
5521
|
customRenderers: options.markdown?.customRenderers
|
|
5129
5522
|
}
|
|
@@ -5249,6 +5642,7 @@ var ReportGenerator = class {
|
|
|
5249
5642
|
includeSummaryTable: this.options.markdown.includeSummaryTable,
|
|
5250
5643
|
permalinkBaseUrl: this.options.markdown.permalinkBaseUrl,
|
|
5251
5644
|
ticketUrlTemplate: this.options.markdown.ticketUrlTemplate,
|
|
5645
|
+
traceUrlTemplate: this.options.markdown.traceUrlTemplate,
|
|
5252
5646
|
includeSourceLinks: this.options.markdown.includeSourceLinks,
|
|
5253
5647
|
customRenderers: this.options.markdown.customRenderers
|
|
5254
5648
|
});
|