mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-31 06:04:11 +01:00
179 lines
18 KiB
TypeScript
179 lines
18 KiB
TypeScript
import { paperclipReportLogo } from "./report-brand.js";
|
||
import type { ReportExecution } from "./report-catalog.js";
|
||
import type { RunnerE2EHistoryIndex } from "./types.js";
|
||
|
||
export function reportHtml(value: unknown) {
|
||
return String(value ?? "").replace(/[&<>"']/g, character =>
|
||
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[character]!,
|
||
);
|
||
}
|
||
|
||
export function reportHistoryHref(history: RunnerE2EHistoryIndex | undefined, campaignId: string | undefined) {
|
||
const url = history?.campaigns.find(campaign => campaign.campaignId === campaignId)?.publicUrl;
|
||
return url && /campaigns\/[^/]+\/(?:index\.html)?$/.test(url)
|
||
? url.replace(/campaigns\/[^/]+\/(?:index\.html)?$/, "")
|
||
: "https://d1p6rlowie26tp.cloudfront.net/runner-e2e/";
|
||
}
|
||
|
||
export function renderReportShell(input: {
|
||
campaignId?: string;
|
||
catalog?: readonly ReportExecution[];
|
||
history?: RunnerE2EHistoryIndex;
|
||
historySuites?: readonly { id: string; label: string; campaignCount: number }[];
|
||
}) {
|
||
const h = reportHtml;
|
||
const campaign = Boolean(input.catalog);
|
||
const label = input.campaignId ?? "Current campaign";
|
||
const root = campaign ? reportHistoryHref(input.history, input.campaignId) : "./";
|
||
const suites = [...new Map((input.catalog ?? []).map(row => [row.suite.id, row.suite])).values()];
|
||
const tree = campaign ? `<nav class="report-suite-tree" aria-label="Campaign suites"><p class="report-side-label">Suites</p>${suites.map(suite => {
|
||
const cases = [...new Map(input.catalog!.filter(row => row.suite.id === suite.id).map(row => [row.task.id, row])).values()];
|
||
return `<details data-nav-suite="${h(suite.id)}"><summary><a href="#suite-${h(suite.id)}" data-nav-suite-link="${h(suite.id)}">${h(suite.label)}<small>${cases.length}</small></a></summary><ul>${cases.map(row => `<li><a href="?view=case&case=${encodeURIComponent(`${suite.id}.${row.task.id}`)}#execution-${h(row.id)}" data-nav-case="${h(`${suite.id}.${row.task.id}`)}" data-report-case-link="${h(`${suite.id}.${row.task.id}`)}"><span class="case-status-dot" aria-hidden="true"></span><span>${h(row.task.label)}</span></a></li>`).join("")}</ul></details>`;
|
||
}).join("")}</nav>` : input.historySuites ? `<nav class="report-suite-catalog" aria-label="E2E suites"><p class="report-side-label">Suites</p><input type="search" data-index-suite-query aria-label="Find suites" placeholder="Find suites…">${input.historySuites.map(suite => `<a href="?view=history&suite=${encodeURIComponent(suite.id)}" data-index-suite-link="${h(suite.id)}"><span>${h(suite.label)}</span><small title="${suite.campaignCount} campaigns">${suite.campaignCount}</small></a>`).join("")}</nav>` : "";
|
||
const presets = [...new Set(input.history?.campaigns.map(row => row.preset).filter((preset): preset is string => Boolean(preset)) ?? [])];
|
||
return `<div class="report-shell" data-report-scope="${campaign ? "campaign" : "global"}" data-campaign-label="${h(label)}">
|
||
<button class="report-drawer-backdrop" type="button" data-nav-close aria-label="Close navigation" hidden></button>
|
||
<aside class="report-sidebar" id="report-navigation" aria-label="Report navigation">
|
||
<a class="brand-lockup report-brand" href="https://pages.paperclip.ing/evals/" aria-label="Paperclip evaluations">${paperclipReportLogo}<small>Evaluations</small></a>
|
||
<div class="report-sidebar-scroll"><nav class="report-global-nav" aria-label="Evaluations">
|
||
<a href="${campaign ? "?view=all" : "?view=overview"}" data-nav-view="${campaign ? "all" : "overview"}" aria-current="page"><span aria-hidden="true">◫</span>${campaign ? "All results" : "Overview"}</a>
|
||
<a href="https://d1p6rlowie26tp.cloudfront.net/runner-e2e/campaigns/chat-catalog-overview-20261029-175400z/" data-nav-catalog><span aria-hidden="true">▦</span>Chat catalog · Oct 29</a>
|
||
<a href="?view=attention" data-nav-view="attention"><span aria-hidden="true">○</span>Needs attention</a>
|
||
<a href="${h(root)}?view=history" ${campaign ? "" : 'data-nav-view="history"'}><span aria-hidden="true">↶</span>Campaign history</a>
|
||
</nav>${!campaign && presets.length ? `<nav class="report-global-nav" aria-label="Run presets"><p class="report-side-label">Run presets</p>${presets.map(preset => `<a href="?view=history&preset=${encodeURIComponent(preset)}" data-index-preset-link="${h(preset)}">${h(preset === "qa-smoke" ? "QA smoke" : preset)}</a>`).join("")}</nav>` : ""}${tree}</div><div class="report-sidebar-footer">E2E tests${campaign ? `<span>${input.catalog!.length} executions</span>` : ""}</div>
|
||
</aside><div class="report-surface">
|
||
<header class="report-topbar"><button type="button" class="report-menu-toggle" data-nav-toggle aria-label="Open navigation" aria-controls="report-navigation" aria-expanded="false">☰</button>
|
||
<nav class="report-breadcrumbs" aria-label="Breadcrumb" data-nav-breadcrumbs><a href="${h(root)}?view=history">Campaigns</a>${campaign ? `<span aria-hidden="true">/</span><span>${h(label)}</span><span aria-hidden="true">/</span><span aria-current="page">All results</span>` : '<span aria-hidden="true">/</span><span aria-current="page">Overview</span>'}</nav>
|
||
${campaign && input.history?.campaigns.length ? `<select class="report-campaign-select" data-nav-campaign aria-label="Campaign">${!input.history.campaigns.some(item => item.campaignId === input.campaignId) ? `<option selected value="">${h(label)}</option>` : ""}${input.history.campaigns.map(item => `<option value="${h(item.publicUrl)}" ${item.campaignId === input.campaignId ? "selected" : ""}>${h(item.campaignId)}</option>`).join("")}</select>` : ""}
|
||
</header>`;
|
||
}
|
||
|
||
export const reportShellScript = `
|
||
(() => {
|
||
const shell = document.querySelector('.report-shell');
|
||
const toggle = document.querySelector('[data-nav-toggle]');
|
||
const backdrop = document.querySelector('[data-nav-close]');
|
||
const close = () => { shell.classList.remove('navigation-open'); toggle.setAttribute('aria-expanded', 'false'); backdrop.hidden = true; };
|
||
toggle.addEventListener('click', () => { const open = !shell.classList.contains('navigation-open'); shell.classList.toggle('navigation-open', open); toggle.setAttribute('aria-expanded', String(open)); backdrop.hidden = !open; });
|
||
backdrop.addEventListener('click', close);
|
||
shell.querySelectorAll('.report-sidebar a').forEach(link => link.addEventListener('click', close));
|
||
document.addEventListener('keydown', event => { if(event.key === 'Escape' && shell.classList.contains('navigation-open')) { close(); toggle.focus(); } });
|
||
document.querySelector('[data-nav-campaign]')?.addEventListener('change', event => { if(event.target.value) location.href = event.target.value; });
|
||
})();
|
||
`;
|
||
|
||
export const reportNavigationStyles = `
|
||
:root { --report-sidebar-width: 250px; --navigation-sticky-offset: 64px; }
|
||
:root { --report-filter-active:#2563eb; --report-filter-tint:#eff6ff; }
|
||
@media(prefers-color-scheme:dark) { :root { --report-filter-active:#60a5fa; --report-filter-tint:#172235; } }
|
||
.report-shell [data-filter-active=true] { border-color:var(--report-filter-active); background-color:var(--report-filter-tint); box-shadow:0 0 0 1px var(--report-filter-active); }
|
||
.report-shell label:has([data-filter-active=true]) > span { color:var(--report-filter-active); }
|
||
html { scroll-behavior: auto; }
|
||
[hidden] { display: none !important; }
|
||
.report-shell { min-height: 100dvh; }
|
||
.report-surface { margin-left: var(--report-sidebar-width); min-width: 0; }
|
||
.report-surface main { width: 100%; max-width: 1320px; margin: 0 auto; padding: 32px 36px 72px; }
|
||
.report-sidebar { position: fixed; inset: 0 auto 0 0; width: var(--report-sidebar-width); display: flex; flex-direction: column; background: var(--card, var(--raised)); border-right: 1px solid var(--border); z-index: 20; }
|
||
.report-brand { min-height: 68px; padding: 20px 16px; display: flex; align-items: center; flex-wrap: wrap; gap: 6px 9px; font-size: 14px; text-decoration: none; }
|
||
.report-brand small { font-size: 10px; font-weight: 400; margin-left: auto; color: var(--muted-foreground); }
|
||
.report-brand svg { width: auto; height: 24px; flex: none; }
|
||
.report-sidebar-scroll { min-height: 0; flex: 1; overflow: auto; padding: 0 10px 24px; scrollbar-width: thin; }
|
||
.report-global-nav { display: grid; gap: 3px; }
|
||
.report-global-nav a { display: flex; gap: 11px; align-items: center; padding: 9px 10px; font-size: 12px; color: var(--muted-foreground); border-radius: 5px; text-decoration: none; }
|
||
.report-global-nav a:hover,.report-suite-tree a:hover { background: var(--raised); color: var(--foreground); }
|
||
.report-global-nav a[aria-current=page] { color: var(--foreground); background: var(--raised); }
|
||
.report-side-label { padding: 18px 10px 7px; margin: 0; font: 10px var(--font-mono); color: var(--muted-foreground); text-transform: uppercase; letter-spacing: .08em; }
|
||
.report-suite-catalog { display: grid; gap: 2px; }
|
||
.report-suite-catalog input { min-width: 0; width: calc(100% - 12px); margin: 0 6px 7px; padding: 8px; border: 1px solid var(--border); border-radius: 5px; color: var(--foreground); background: var(--background); font: 12px var(--font-sans); }
|
||
.report-suite-catalog a { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; padding: 8px 10px; color: var(--muted-foreground); text-decoration: none; border-radius: 5px; font-size: 12px; }
|
||
.report-suite-catalog a span { min-width: 0; overflow-wrap: anywhere; }
|
||
.report-suite-catalog a small { flex: none; font: 10px var(--font-mono); }
|
||
.report-suite-catalog a:hover,.report-suite-catalog a[aria-current=page] { color: var(--foreground); background: var(--raised); }
|
||
.report-suite-tree details > summary { display: flex; align-items: center; gap: 4px; list-style: none; cursor: pointer; padding-left: 4px; }
|
||
.report-suite-tree summary::-webkit-details-marker { display: none; }
|
||
.report-suite-tree summary::before { content: '›'; width: 12px; flex: none; color: var(--muted-foreground); }
|
||
.report-suite-tree details[open] > summary::before { content: '⌄'; }
|
||
.report-suite-tree a { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1; padding: 8px 5px; border-radius: 5px; text-decoration: none; font-size: 12px; color: var(--muted-foreground); }
|
||
.report-suite-tree a small { margin-left: auto; font: 10px var(--font-mono); }
|
||
.report-suite-tree a[aria-current=page],.report-suite-tree a.in-view { color: var(--foreground); background: var(--raised); }
|
||
.report-suite-tree ul { display: grid; gap: 2px; margin: 0; padding: 0 0 8px 22px; list-style: none; }
|
||
.report-suite-tree ul a { font-size: 11px; align-items: baseline; }
|
||
.report-suite-tree .case-status-dot { flex: 0 0 5px; width: 5px; height: 5px; }
|
||
.report-sidebar-footer { display: flex; justify-content: space-between; gap: 10px; border-top: 1px solid var(--border); color: var(--muted-foreground); font-size: 10px; padding: 17px 20px; }
|
||
header.report-topbar { position: sticky; top: 0; z-index: 12; display: flex; align-items: center; justify-content: space-between; flex-wrap: nowrap; min-height: 64px; gap: 20px; margin: 0; padding: 12px 36px; border-bottom: 1px solid var(--border); background: var(--background); }
|
||
.report-breadcrumbs { display: flex; align-items: center; flex-wrap: wrap; gap: 5px 8px; font-size: 11px; min-width: 0; color: var(--muted-foreground); }
|
||
.report-breadcrumbs a { text-decoration: none; }
|
||
.report-breadcrumbs a:hover { text-decoration: underline; }
|
||
.report-breadcrumbs [aria-current=page] { color: var(--foreground); }
|
||
.report-breadcrumbs a,.report-breadcrumbs span { overflow-wrap: anywhere; }
|
||
.report-campaign-select { font: 11px var(--font-sans); padding: 8px; color: var(--foreground); background: var(--card); border: 1px solid var(--border); border-radius: 5px; width: 220px; max-width: 30%; flex: none; }
|
||
.report-shell .report-surface select {
|
||
appearance: none;
|
||
-webkit-appearance: none;
|
||
padding-inline-end: 34px;
|
||
background-image: linear-gradient(45deg, transparent 50%, var(--muted-foreground) 50%), linear-gradient(135deg, var(--muted-foreground) 50%, transparent 50%);
|
||
background-position: calc(100% - 16px) center, calc(100% - 12px) center;
|
||
background-size: 4px 4px;
|
||
background-repeat: no-repeat;
|
||
}
|
||
.report-menu-toggle { display: none; border: 0; padding: 0; background: transparent; color: var(--foreground); font-size: 20px; cursor: pointer; }
|
||
.report-drawer-backdrop { display: none; }
|
||
.report-header h1 { font-size: 32px; }
|
||
.report-header .lede { font-size: 12px; }
|
||
.report-source { display: flex; flex-wrap: wrap; gap: 12px; color: var(--muted-foreground); font-size: 11px; }
|
||
.report-header { align-items: start; gap: 24px; }
|
||
.report-actions { flex-wrap: wrap; }
|
||
.campaign-billing-details { margin: 8px 0 24px; }
|
||
.campaign-billing-details > summary { cursor: pointer; color: var(--muted-foreground); font-size: 12px; }
|
||
[data-campaign-overview] .campaign-suites { grid-template-columns: repeat(2,minmax(0,1fr)); gap: 0 28px; }
|
||
[data-campaign-overview] .campaign-suite-row { grid-template-columns: minmax(0,1fr) auto; gap: 4px 16px; }
|
||
[data-campaign-overview] .campaign-suite-state { grid-column: 1/-1; text-align: left; font-size: 10px; }
|
||
.report-surface .report-header { grid-template-columns: 1fr; }
|
||
.report-surface .results-heading { scroll-margin-top: calc(var(--navigation-sticky-offset) + 24px); }
|
||
.report-surface .suite-section { scroll-margin-top: calc(var(--navigation-sticky-offset) + 24px); margin-top: 56px; }
|
||
.report-surface .section-heading h2 { font-size: 26px; }
|
||
.report-surface .suite-case-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||
.report-journey { margin: 32px 0 44px; scroll-margin-top: calc(var(--navigation-sticky-offset) + 24px); }
|
||
.report-journey-heading { display: flex; justify-content: space-between; align-items: baseline; gap: 20px; margin: 0 0 14px; }
|
||
.report-journey-heading h3 { margin: 0; font-size: 15px; font-weight: 600; }
|
||
.report-journey-heading a { text-decoration: none; }
|
||
.report-journey-heading a:hover { text-decoration: underline; }
|
||
.report-journey-heading > a { font-size: 11px; color: var(--muted-foreground); white-space: nowrap; }
|
||
.execution-previews { display: grid; grid-template-columns: repeat(auto-fit,minmax(min(100%,390px),1fr)); gap: 20px; align-items: start; }
|
||
.execution-preview { min-width: 0; }
|
||
.execution-preview-label { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; font-size: 10px; color: var(--muted-foreground); }
|
||
.execution-preview-label strong { color: var(--foreground); font-weight: 500; }
|
||
.report-surface .profile-sticky { position: static; }
|
||
.execution-preview .case { scroll-margin-top: calc(var(--navigation-sticky-offset) + 24px); }
|
||
.case-open { text-decoration: none; }
|
||
.case-open:hover { text-decoration: underline; }
|
||
.case-config-options { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 16px; }
|
||
.case-config-options button { display: flex; align-items: center; gap: 7px; cursor: pointer; padding: 7px 9px; font-size: 11px; color: var(--muted-foreground); background: var(--background); border: 1px solid var(--border); border-radius: 5px; }
|
||
.case-config-options button[aria-pressed=true] { color: var(--foreground); background: var(--raised); border-color: var(--border-strong); }
|
||
.case-config-options button:focus-visible,.report-menu-toggle:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }
|
||
.case-page-heading { margin-bottom: 26px; }
|
||
.case-page-heading h1 { font-size: 28px; margin-bottom: 12px; }
|
||
.case-page-heading a { font-size: 12px; color: var(--muted-foreground); }
|
||
.report-shell[data-view=case] .report-journey-heading,.report-shell[data-view=case] .suite-case-toc,.report-shell[data-view=case] .suite-section > .section-heading,.report-shell[data-view=case] .suite-summary,.report-shell[data-view=case] .execution-preview-label { display: none; }
|
||
.report-shell[data-view=case] .suite-section,.report-shell[data-view=case] .report-journey { margin: 0; }
|
||
.report-shell[data-view=case] .execution-previews { display: block; }
|
||
.report-shell[data-view=case] .case { scroll-margin-top: calc(var(--navigation-sticky-offset) + 24px); }
|
||
@media(max-width:1100px) { :root { --report-sidebar-width: 220px; }.report-surface main { padding: 28px 24px 60px; }header.report-topbar { padding: 12px 24px; }.report-campaign-select { max-width: 35%; }.report-surface .suite-summary { grid-template-columns: repeat(3,minmax(0,1fr)); } }
|
||
@media(max-width:760px) {
|
||
.report-surface { margin-left: 0; }.report-surface main { padding: 24px 17px 56px; }
|
||
.report-sidebar { display: none; width: min(290px,85vw); }.navigation-open .report-sidebar { display: flex; }
|
||
.navigation-open .report-drawer-backdrop { display: block; position: fixed; inset: 0; border: 0; background: var(--overlay,#0009); z-index: 19; }
|
||
header.report-topbar { display: grid; grid-template-columns: 24px minmax(0,1fr); gap: 10px; padding: 12px 17px; }
|
||
.report-menu-toggle { display: block; grid-row: 1; grid-column: 1; }.report-campaign-select { grid-row: 1; grid-column: 2; justify-self: end; max-width: 100%; width: 220px; }
|
||
.report-breadcrumbs { grid-column: 1/-1; font-size: 10px; }
|
||
.report-surface .suite-case-list { grid-template-columns: 1fr; }
|
||
[data-campaign-overview] .campaign-suites { grid-template-columns: 1fr; }
|
||
.report-surface .suite-summary { grid-template-columns: repeat(2,minmax(0,1fr)); }
|
||
.report-surface .report-header h1 { font-size: 28px; }.report-surface .section-heading h2 { font-size: 24px; }
|
||
.execution-previews { grid-template-columns: 1fr; }.report-journey-heading { align-items: start; }.report-journey-heading > a { max-width: 80px; white-space: normal; text-align: right; }
|
||
.report-surface .report-filters input,.report-surface .report-filters select { font-size: 16px; }
|
||
.report-shell[data-view=case] .gallery { grid-template-columns: 1fr; }
|
||
}
|
||
@media print { .report-sidebar,.report-topbar { display: none; }.report-surface { margin-left: 0; }.report-surface main { padding: 0; } }
|
||
`;
|