@@ -45,7 +63,7 @@ export function InsightsTab({ workbenchTenantId }: { readonly workbenchTenantId:
Last 7 days
- {tiles.map(([label, value]) => (
+ {benchInsightTiles(stats, saved).map(([label, value]) => (
{value}
{label}
@@ -56,24 +74,39 @@ export function InsightsTab({ workbenchTenantId }: { readonly workbenchTenantId:
Runs per day
+
+ Open insights
+
-
-
-
-
- Open Insights
-
+ {failed.length === 0 ? null : (
+
+
+
Needs a look
+
+
+ {failed.map((run, i) => {
+ const events = failureEvents[i]?.data?.events;
+ const why = events === undefined ? null : runFailureMessage(events);
+ return (
+
+
+
+ {runDisplayName(run)} failed
+ {why ?? "The run failed."}
+
+ {formatWhen(run.createdAt)}
+
+ );
+ })}
+
+
+ )}
);
}
diff --git a/apps/web/src/bench/workflows-tab.tsx b/apps/web/src/bench/workflows-tab.tsx
index 0ee8e1133..b8ff6dacf 100644
--- a/apps/web/src/bench/workflows-tab.tsx
+++ b/apps/web/src/bench/workflows-tab.tsx
@@ -24,7 +24,7 @@ export function WorkflowsTab({ workbenchTenantId }: { readonly workbenchTenantId
Scheduled here
- See all
+ Open all
{flows.isLoading ? : null}
{flows.isError ? (
diff --git a/apps/web/src/insights-stats.ts b/apps/web/src/insights-stats.ts
index ec4332bb1..8d4a6b302 100644
--- a/apps/web/src/insights-stats.ts
+++ b/apps/web/src/insights-stats.ts
@@ -215,6 +215,29 @@ export function computeBenchInsights(
};
}
+/** Non-archived artifacts created on or after `sinceMs`. */
+export function countSavedArtifacts(
+ artifacts: readonly { readonly archivedAt: string | null; readonly createdAt: string }[],
+ sinceMs: number,
+): number {
+ return artifacts.filter((a) => a.archivedAt === null && Date.parse(a.createdAt) >= sinceMs)
+ .length;
+}
+
+/** The four headline tiles shared by the Insights page and the drawer tab. */
+export function benchInsightTiles(
+ stats: BenchInsights,
+ saved: string,
+): readonly (readonly [string, string])[] {
+ const finished = stats.ok + stats.fail;
+ return [
+ ["Runs", formatCount(stats.total)],
+ ["Succeeded", finished === 0 ? "—" : `${Math.round((stats.ok / finished) * 100)}%`],
+ ["Median run", stats.medianMs === null ? "—" : durationLabel(stats.medianMs)],
+ ["Artifacts saved", saved],
+ ];
+}
+
/** Most recent in-range runs whose approvals the Insights cards fetch. */
export const APPROVAL_RUN_CAP = 50;
diff --git a/apps/web/src/pages/bench-insights.tsx b/apps/web/src/pages/bench-insights.tsx
index 2e498d7c9..95c711ff7 100644
--- a/apps/web/src/pages/bench-insights.tsx
+++ b/apps/web/src/pages/bench-insights.tsx
@@ -19,7 +19,9 @@ import {
import {
approvalRunsInRange,
BENCH_RANGES,
+ benchInsightTiles,
computeBenchInsights,
+ countSavedArtifacts,
durationLabel,
formatCount,
runDisplayName,
@@ -57,7 +59,14 @@ function dayLabel(date: Date): string {
});
}
-export function OutcomeChart({ days }: { readonly days: readonly BenchDay[] }) {
+export function OutcomeChart({
+ days,
+ weekdayAxis = false,
+}: {
+ readonly days: readonly BenchDay[];
+ /** Weekday initials on the axis, for the drawer's 7-day chart. */
+ readonly weekdayAxis?: boolean;
+}) {
const [active, setActive] = useState(null);
const max = Math.max(...days.map((d) => d.ok + d.fail + d.other), 1);
const every = days.length > 30 ? 14 : days.length > 7 ? 5 : 1;
@@ -115,7 +124,11 @@ export function OutcomeChart({ days }: { readonly days: readonly BenchDay[] }) {
{days.map((d, i) => (
- {i % every === 0 ? `${d.date.getMonth() + 1}/${d.date.getDate()}` : ""}
+ {weekdayAxis
+ ? d.date.toLocaleDateString("en-US", { weekday: "narrow" })
+ : i % every === 0
+ ? `${d.date.getMonth() + 1}/${d.date.getDate()}`
+ : ""}
))}
@@ -244,7 +257,6 @@ function InsightsBody({
}) {
const stats = computeBenchInsights(runs.data, range);
const approvalRuns = approvalRunsInRange(runs.data, range);
- const finished = stats.ok + stats.fail;
const artifacts = useAPIQuery(`/api/tenants/${tenantId}/artifacts`, ArtifactListPageSchema);
const schedules = useQuery({
queryKey: tenantKeys.schedules(tenantId),
@@ -263,18 +275,9 @@ function InsightsBody({
const rangeStart = stats.days[0]?.date.getTime() ?? 0;
const savedCount =
artifacts.kind === "ready"
- ? formatCount(
- artifacts.data.artifacts.filter(
- (a) => a.archivedAt === null && Date.parse(a.createdAt) >= rangeStart,
- ).length,
- )
+ ? formatCount(countSavedArtifacts(artifacts.data.artifacts, rangeStart))
: "—";
- const tiles: readonly (readonly [string, string])[] = [
- ["Runs", formatCount(stats.total)],
- ["Succeeded", finished === 0 ? "—" : `${Math.round((stats.ok / finished) * 100)}%`],
- ["Median run", stats.medianMs === null ? "—" : durationLabel(stats.medianMs)],
- ["Artifacts saved", savedCount],
- ];
+ const tiles = benchInsightTiles(stats, savedCount);
return (