Skip to content

Commit 0ff17ea

Browse files
GabrielDraporclaude
andcommitted
test(viewer): snapshot the real SpanInfoPanel for a user trace with a user and session
The chrome snapshot mocks SpanInfoPanel away, and its chip row is the one place the agent-trace work touched the customer path. Pin the real panel for an RCA-less customer trace carrying user_id + session_id: User:/Session: chips, nothing beside them. The header states the intended scope — a customer trace with an available RCA gains the "Root cause analysis" chip on purpose. The existing byte-compare stays untouched. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
1 parent 981c3ef commit 0ff17ea

2 files changed

Lines changed: 110 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`user-trace span panel (real SpanInfoPanel) is unchanged by agent-trace work > matches the committed snapshot for a trace with a user and session and no RCA 1`] = `"<div class="animate-slide-in-right fixed bottom-0 right-0 z-50 border-l border-border bg-background shadow-xl transition-[width,top] duration-200 top-0 w-[70%]"><div class="flex h-full flex-col bg-background"><div class="flex h-12 items-center justify-between border-b border-border bg-muted/30 px-4"><div class="flex min-w-0 items-center gap-1.5"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-workflow h-4 w-4 shrink-0 text-muted-foreground"><rect width="8" height="8" x="3" y="3" rx="2"></rect><path d="M7 11v4a2 2 0 0 0 2 2h4"></path><rect width="8" height="8" x="13" y="13" rx="2"></rect></svg><span class="shrink-0 text-sm font-medium">Trace</span><span class="truncate font-mono text-xs text-muted-foreground">t1</span></div><div class="flex items-center gap-1"><button class="inline-flex items-center justify-center whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground rounded-md text-xs h-7 w-7 p-0" disabled="" title="Previous trace"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-up h-4 w-4"><path d="m5 12 7-7 7 7"></path><path d="M12 19V5"></path></svg></button><button class="inline-flex items-center justify-center whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground rounded-md text-xs h-7 w-7 p-0" disabled="" title="Next trace"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-down h-4 w-4"><path d="M12 5v14"></path><path d="m19 12-7 7-7-7"></path></svg></button><button class="inline-flex items-center justify-center whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground rounded-md text-xs h-7 w-7 p-0" title="Expand to full screen"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-expand h-4 w-4"><path d="m21 21-6-6m6 6v-4.8m0 4.8h-4.8"></path><path d="M3 16.2V21m0 0h4.8M3 21l6-6"></path><path d="M21 7.8V3m0 0h-4.8M21 3l-6 6"></path><path d="M3 7.8V3m0 0h4.8M3 3l6 6"></path></svg></button><button class="inline-flex items-center justify-center whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground rounded-md text-xs h-7 w-7 p-0" title="Open in new tab"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-arrow-out-up-right h-4 w-4"><path d="M21 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h6"></path><path d="m21 3-9 9"></path><path d="M15 3h6v6"></path></svg></button><div class="w-2"></div><button class="inline-flex items-center justify-center whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground rounded-md text-xs h-7 w-7 p-0" title="AI Assistant"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bot-message-square h-4 w-4"><path d="M12 6V2H8"></path><path d="m8 18-4 4V8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2Z"></path><path d="M2 12h2"></path><path d="M9 11v2"></path><path d="M15 11v2"></path><path d="M20 12h2"></path></svg></button><button class="inline-flex items-center justify-center whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground rounded-md text-xs h-7 w-7 p-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x h-4 w-4"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg></button></div></div><div class="flex h-10 items-center border-b border-border"><div class="flex items-center rounded-lg px-2 py-1"><button class="flex items-center gap-2 rounded-md px-3 py-1 text-xs font-medium transition-all bg-muted text-foreground shadow-sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-tree h-3.5 w-3.5"><path d="M21 12h-8"></path><path d="M21 6H8"></path><path d="M21 18h-8"></path><path d="M3 6v4c0 1.1.9 2 2 2h3"></path><path d="M3 10v6c0 1.1.9 2 2 2h3"></path></svg> Trace</button><button class="flex items-center gap-2 rounded-md px-3 py-1 text-xs font-medium transition-all text-muted-foreground hover:text-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-chart-gantt h-3.5 w-3.5"><rect width="18" height="18" x="3" y="3" rx="2"></rect><path d="M9 8h7"></path><path d="M8 12h6"></path><path d="M11 16h5"></path></svg> Timeline</button><button class="flex items-center gap-2 rounded-md px-3 py-1 text-xs font-medium transition-all text-muted-foreground hover:text-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye h-3.5 w-3.5"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"></path><circle cx="12" cy="12" r="3"></circle></svg> Detectors</button></div></div><div class="relative flex flex-1 overflow-hidden"><div><div><div><div><div class="flex flex-shrink-0 items-center border-b border-border bg-muted/10 px-3" style="height: 28px;"><span class="text-[11px] font-medium text-muted-foreground">Trace Tree</span></div><div class="flex-1 overflow-y-auto overflow-x-hidden"><div data-testid="tree"></div></div></div><div><div class="h-full overflow-y-auto"><div class="sticky top-0 z-10 border-b border-border bg-background px-4 py-3"><div class="mb-3 flex items-center justify-between gap-3"><div class="min-w-0"><div class="mb-1 flex items-center gap-2"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-workflow h-4 w-4 text-current"><rect width="8" height="8" x="3" y="3" rx="2"></rect><path d="M7 11v4a2 2 0 0 0 2 2h4"></path><rect width="8" height="8" x="13" y="13" rx="2"></rect></svg><h3 class="text-sm font-medium">agent_turn</h3><button class="inline-flex items-center justify-center whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 hover:bg-accent rounded-md text-xs p-0 h-6 w-6 text-muted-foreground hover:text-foreground" title="Copy ID"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy h-3.5 w-3.5"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></div><div class="text-xs text-muted-foreground">2026-01-01 00:00:00</div></div></div><div class="flex flex-wrap items-center gap-2"><div class="inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs"><span class="text-muted-foreground">Span Kind:</span><span class="font-medium">trace</span></div><div class="inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clock h-3 w-3 text-muted-foreground"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg><span class="text-muted-foreground">Latency:</span><span class="font-medium">1.0s</span></div><div class="inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs" data-state="closed"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-stop h-3 w-3 text-muted-foreground"><circle cx="12" cy="12" r="10"></circle><rect x="9" y="9" width="6" height="6" rx="1"></rect></svg><span class="font-medium">0 → 0 (0)</span></div></div><div class="mt-2 flex flex-wrap items-center gap-2"><button type="button" class="inline-flex cursor-pointer items-center gap-1.5 rounded-md border bg-muted/40 py-1 pl-2.5 pr-1.5 text-xs transition-colors hover:bg-muted"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-users h-3 w-3 text-muted-foreground"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M22 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg><span class="text-muted-foreground">User:</span><span class="font-medium">user-42</span><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right h-3 w-3 text-muted-foreground"><path d="m9 18 6-6-6-6"></path></svg></button><button type="button" class="inline-flex cursor-pointer items-center gap-1.5 rounded-md border bg-muted/40 py-1 pl-2.5 pr-1.5 text-xs transition-colors hover:bg-muted"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-layers h-3 w-3 text-muted-foreground"><path d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z"></path><path d="M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12"></path><path d="M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17"></path></svg><span class="text-muted-foreground">Session:</span><span class="font-medium">sess-7</span><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right h-3 w-3 text-muted-foreground"><path d="m9 18 6-6-6-6"></path></svg></button></div></div><div class="space-y-3 p-4"><div class="overflow-hidden rounded-md border border-border"><button class="flex w-full items-center justify-between border-b border-border bg-muted/50 px-2.5 py-1.5 transition-colors hover:bg-muted"><div class="flex items-center gap-1.5"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down h-3.5 w-3.5 text-muted-foreground"><path d="m6 9 6 6 6-6"></path></svg><span class="text-xs font-medium text-foreground">Input</span></div><div class="flex items-center gap-1"></div></button><div class="bg-background px-2.5 py-2"><span class="text-[11px] text-muted-foreground">-</span></div></div><div class="overflow-hidden rounded-md border border-border"><button class="flex w-full items-center justify-between border-b border-border bg-muted/50 px-2.5 py-1.5 transition-colors hover:bg-muted"><div class="flex items-center gap-1.5"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down h-3.5 w-3.5 text-muted-foreground"><path d="m6 9 6 6 6-6"></path></svg><span class="text-xs font-medium text-foreground">Output</span></div><div class="flex items-center gap-1"></div></button><div class="bg-background px-2.5 py-2"><span class="text-[11px] text-muted-foreground">-</span></div></div><div class="overflow-hidden rounded-md border border-border"><button class="flex w-full items-center justify-between border-b border-border bg-muted/50 px-2.5 py-1.5 transition-colors hover:bg-muted"><div class="flex items-center gap-1.5"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down h-3.5 w-3.5 text-muted-foreground"><path d="m6 9 6 6 6-6"></path></svg><span class="text-xs font-medium text-foreground">Metadata</span></div><div class="flex items-center gap-1"></div></button><div class="bg-background px-2.5 py-2"><span class="text-[11px] text-muted-foreground">-</span></div></div></div></div></div></div></div></div></div></div></div>"`;
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// @vitest-environment jsdom
2+
//
3+
// Companion to trace-viewer-user-snapshot.test.tsx, which pins the viewer's
4+
// own chrome but mocks SpanInfoPanel away — and SpanInfoPanel's chip row is
5+
// the one place the agent-trace work touched the customer path. This
6+
// snapshot renders the REAL SpanInfoPanel for a customer trace that carries a
7+
// user and a session and has no RCA, so the User:/Session: chips are pinned
8+
// with nothing new beside them. Never re-generate it (`-u`) once agent-trace
9+
// changes land.
10+
//
11+
// Scope, stated plainly: "byte-identical" holds for customer traces WITHOUT
12+
// an available RCA. A customer trace whose finding has a completed, exported
13+
// analysis intentionally gains the "Root cause analysis" chip in this row —
14+
// that is the feature's entry point, not a leak.
15+
process.env.TZ = "UTC";
16+
17+
import { describe, expect, it, vi } from "vitest";
18+
import { render } from "@testing-library/react";
19+
20+
const trace = {
21+
trace_id: "t1",
22+
project_id: "p1",
23+
name: "agent_turn",
24+
source: "user",
25+
user_id: "user-42",
26+
session_id: "sess-7",
27+
trace_start_time: "2026-01-01T00:00:00Z",
28+
trace_end_time: "2026-01-01T00:00:01Z",
29+
spans: [
30+
{
31+
span_id: "s1",
32+
parent_span_id: null,
33+
name: "agent_turn",
34+
span_kind: "AGENT",
35+
status: "OK",
36+
span_start_time: "2026-01-01T00:00:00Z",
37+
span_end_time: "2026-01-01T00:00:01Z",
38+
},
39+
],
40+
};
41+
42+
vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn() }) }));
43+
vi.mock("@tanstack/react-query", () => ({
44+
useQuery: () => ({ data: trace, isLoading: false, error: null }),
45+
}));
46+
vi.mock("@/lib/api", () => ({ getTrace: vi.fn() }));
47+
vi.mock("../../hooks/use-trace-stream", () => ({ useTraceStream: vi.fn() }));
48+
// SpanInfoPanel lazily fetches the selection's I/O; nothing to fetch here.
49+
vi.mock("../../hooks", async (importOriginal) => ({
50+
...(await importOriginal<typeof import("../../hooks")>()),
51+
useSpanIO: () => ({ data: undefined, isLoading: false, error: null }),
52+
}));
53+
vi.mock("@/features/detectors/hooks/use-findings", () => ({
54+
useTraceFindings: () => ({ data: { findings: [] } }),
55+
useRca: () => ({ data: { rca: null } }),
56+
}));
57+
vi.mock("@/components/layout/app-layout", () => ({
58+
useLayout: () => ({
59+
aiPanelOpen: false,
60+
setAiPanelOpen: vi.fn(),
61+
setAiContext: vi.fn(),
62+
setAiInitialSessionId: vi.fn(),
63+
registerAiHost: () => () => {},
64+
sidebarCollapsed: false,
65+
}),
66+
}));
67+
// Everything but SpanInfoPanel is a stand-in, as in the chrome snapshot.
68+
vi.mock("../SpanTreeView", () => ({ SpanTreeView: () => <div data-testid="tree" /> }));
69+
vi.mock("../SpanTimelineView", () => ({ SpanTimelineView: () => <div data-testid="timeline" /> }));
70+
vi.mock("../TraceDetectorsTab", () => ({
71+
TraceDetectorsTab: () => <div data-testid="detectors-tab" />,
72+
}));
73+
vi.mock("@/features/ai-assistant/components/ai-assistant-panel", () => ({
74+
AiAssistantPanel: () => <div data-testid="ai-panel" />,
75+
}));
76+
vi.mock("@/components/RetentionGateBanner", () => ({
77+
RetentionGateBanner: () => <div data-testid="retention-banner" />,
78+
}));
79+
vi.mock("@/components/ui/resizable", () => ({
80+
ResizablePanelGroup: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
81+
ResizablePanel: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
82+
ResizableHandle: () => null,
83+
}));
84+
85+
import { TraceViewerPanel } from "../TraceViewerPanel";
86+
87+
describe("user-trace span panel (real SpanInfoPanel) is unchanged by agent-trace work", () => {
88+
it("matches the committed snapshot for a trace with a user and session and no RCA", async () => {
89+
const { container, findByText } = render(
90+
<TraceViewerPanel
91+
projectId="p1"
92+
traceId="t1"
93+
onClose={() => {}}
94+
onNavigate={() => {}}
95+
canNavigateUp={false}
96+
canNavigateDown={false}
97+
source="user"
98+
/>,
99+
);
100+
// The chip row is on screen, with exactly the two customer chips.
101+
await findByText("user-42");
102+
await findByText("sess-7");
103+
expect(container.querySelector('[title*="RCA agent run"]')).toBeNull();
104+
expect(container.querySelector('[title*="Back to the trace"]')).toBeNull();
105+
expect(container.innerHTML).toMatchSnapshot();
106+
});
107+
});

0 commit comments

Comments
 (0)