Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 14 additions & 27 deletions mcpjam-inspector/client/src/components/CiEvalsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { useSharedAppState } from "@/state/app-state-context";
import { useCiEvalsRoute, navigateToCiEvalsRoute } from "@/lib/ci-evals-router";
import { aggregateSuite, groupSuitesByTag } from "./evals/helpers";
import { TagAggregationPanel } from "./evals/tag-aggregation-panel";
import { OverviewPanel } from "./evals/overview-panel";
import { useEvalMutations } from "./evals/use-eval-mutations";
import { useEvalQueries } from "./evals/use-eval-queries";
import { useEvalHandlers } from "./evals/use-eval-handlers";
Expand Down Expand Up @@ -261,10 +261,10 @@ export function CiEvalsTab({ convexWorkspaceId }: CiEvalsTabProps) {
selectedSuiteId={selectedSuiteId}
onSelectSuite={handleSelectSuite}
onSelectOverview={handleSelectOverview}
isOverviewSelected={!selectedSuiteId && hasTags}
isOverviewSelected={!selectedSuiteId}
isLoading={queries.isOverviewLoading}
filterTag={filterTag}
hasTags={hasTags}
hasTags={true}
/>
</ResizablePanel>

Expand All @@ -290,30 +290,17 @@ export function CiEvalsTab({ convexWorkspaceId }: CiEvalsTabProps) {
</div>
</div>
) : route.type === "list" || !selectedSuite ? (
hasTags ? (
<TagAggregationPanel
tagGroups={tagGroups.filter((g) => g.tag !== "Untagged")}
allTags={allTags}
filterTag={filterTag}
onFilterTagChange={setFilterTag}
onSelectSuite={handleSelectSuite}
/>
) : (
<div className="flex-1 flex items-center justify-center">
<div className="text-center max-w-md mx-auto p-8">
<div className="w-20 h-20 bg-muted rounded-full flex items-center justify-center mx-auto mb-6">
<GitBranch className="h-10 w-10 text-muted-foreground" />
</div>
<h2 className="text-2xl font-semibold text-foreground mb-2">
Select a suite
</h2>
<p className="text-sm text-muted-foreground">
Choose a CI suite from the sidebar to inspect runs and test
iterations.
</p>
</div>
</div>
)
<OverviewPanel
suites={sdkSuites}
allTags={allTags}
filterTag={filterTag}
onFilterTagChange={setFilterTag}
onSelectSuite={handleSelectSuite}
onRerunSuite={(suiteId) => {
const entry = sdkSuites.find((e) => e.suite._id === suiteId);
if (entry) handlers.handleRerun(entry.suite);
}}
/>
) : queries.isSuiteDetailsLoading ? (
<div className="flex h-full items-center justify-center">
<div className="text-center">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,45 @@ interface CiSuiteListSidebarProps {
hasTags: boolean;
}

function getStatusDot(entry: EvalSuiteOverviewEntry): {
function getStatusInfo(entry: EvalSuiteOverviewEntry): {
label: string;
dotClass: string;
labelClass: string;
} {
const latestRun = entry.latestRun;
if (!latestRun) {
return { label: "No runs", dotClass: "bg-muted-foreground/40" };
return {
label: "No runs",
dotClass: "bg-muted-foreground/40",
labelClass: "text-muted-foreground",
};
}
if (latestRun.status === "running" || latestRun.status === "pending") {
return { label: "Running", dotClass: "bg-warning animate-pulse" };
return {
label: "Running",
dotClass: "bg-warning animate-pulse",
labelClass: "text-warning",
};
}
if (latestRun.result === "passed") {
return { label: "Passed", dotClass: "bg-emerald-500" };
return {
label: "Passed",
dotClass: "bg-emerald-500",
labelClass: "text-emerald-500",
};
}
if (latestRun.result === "failed") {
return { label: "Failed", dotClass: "bg-destructive" };
return {
label: "Failed",
dotClass: "bg-destructive",
labelClass: "text-destructive",
};
}
return { label: latestRun.status, dotClass: "bg-muted-foreground/40" };
return {
label: latestRun.status,
dotClass: "bg-muted-foreground/40",
labelClass: "text-muted-foreground",
};
}

function toPercent(value: number): number {
Expand Down Expand Up @@ -83,7 +104,14 @@ export function CiSuiteListSidebar({
return (
<div className="flex h-full flex-col">
<div className="border-b px-4 py-3">
<h2 className="text-sm font-semibold">Eval suites</h2>
<div className="flex items-center justify-between">
<h2 className="text-sm font-semibold">Eval suites</h2>
{filteredSuites.length > 0 && (
<span className="text-[10px] text-muted-foreground tabular-nums">
{filteredSuites.length}
</span>
)}
</div>
</div>

<div className="flex-1 overflow-y-auto">
Expand All @@ -100,9 +128,19 @@ export function CiSuiteListSidebar({
<div className="min-w-0 flex-1">
<div className="text-sm font-medium">Overview</div>
<div className="text-[11px] text-muted-foreground">
Compare suite groups
Suite health & status
</div>
</div>
{(() => {
const failCount = suites.filter(
(e) => e.latestRun?.result === "failed",
).length;
return failCount > 0 ? (
<span className="shrink-0 flex h-5 min-w-[20px] items-center justify-center rounded-full bg-destructive px-1.5 text-[10px] font-bold text-destructive-foreground">
{failCount}
</span>
) : null;
})()}
</div>
</button>
)}
Expand All @@ -118,7 +156,7 @@ export function CiSuiteListSidebar({
<div>
{filteredSuites.map((entry) => {
const latestRun = entry.latestRun;
const status = getStatusDot(entry);
const status = getStatusInfo(entry);
const trend = entry.passRateTrend
.slice(-12)
.map((value) => toPercent(value));
Expand All @@ -138,13 +176,19 @@ export function CiSuiteListSidebar({
)}
>
<div className="flex items-center gap-2.5">
<div
className={cn(
"h-2 w-2 shrink-0 rounded-full",
status.dotClass,
)}
title={status.label}
/>
<div className="flex flex-col items-center gap-0.5 shrink-0 w-[3.25rem]">
<div
className={cn("h-2 w-2 rounded-full", status.dotClass)}
/>
<span
className={cn(
"text-[9px] font-medium leading-none",
status.labelClass,
)}
>
{status.label}
</span>
</div>
<div className="min-w-0 flex-1">
<div className="truncate text-sm font-medium">
{entry.suite.name || "Untitled suite"}
Expand Down
Loading