mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
feat(lens): type to filter the traces agent dropdown (#44745)
* feat(lens): type to filter the traces agent dropdown * test(lens): cover typing, Enter and clear in the agent filter
This commit is contained in:
parent
6f5ca84f69
commit
6764b9af12
2 changed files with 82 additions and 18 deletions
|
|
@ -0,0 +1,55 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { withNuqsTestingAdapter, type UrlUpdateEvent } from "nuqs/adapters/testing";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { runs } from "./__fixtures__/runs";
|
||||
import { RunsToolbar } from "./RunsToolbar";
|
||||
|
||||
const agentBox = () => screen.getByRole("combobox", { name: "Filter traces by agent" });
|
||||
|
||||
describe("RunsToolbar", () => {
|
||||
it("narrows the agent filter as you type and selects the match", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onUrlUpdate = vi.fn<(event: UrlUpdateEvent) => void>();
|
||||
render(<RunsToolbar query="" onQueryChange={vi.fn()} runs={runs} />, {
|
||||
wrapper: withNuqsTestingAdapter({ onUrlUpdate }),
|
||||
});
|
||||
expect(agentBox()).toHaveAttribute("placeholder", "All agents");
|
||||
await user.click(agentBox());
|
||||
await user.keyboard("tri");
|
||||
expect(screen.getAllByRole("option").map((o) => o.textContent)).toEqual(["triage"]);
|
||||
await user.click(screen.getByRole("option", { name: "triage" }));
|
||||
expect(onUrlUpdate.mock.lastCall?.[0].searchParams.get("agent")).toBe("triage");
|
||||
});
|
||||
|
||||
it("picks the first match on Enter", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onUrlUpdate = vi.fn<(event: UrlUpdateEvent) => void>();
|
||||
render(<RunsToolbar query="" onQueryChange={vi.fn()} runs={runs} />, {
|
||||
wrapper: withNuqsTestingAdapter({ onUrlUpdate }),
|
||||
});
|
||||
await user.click(agentBox());
|
||||
await user.keyboard("res{Enter}");
|
||||
expect(onUrlUpdate.mock.lastCall?.[0].searchParams.get("agent")).toBe("researcher");
|
||||
});
|
||||
|
||||
it("says when no agent matches", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<RunsToolbar query="" onQueryChange={vi.fn()} runs={runs} />, { wrapper: withNuqsTestingAdapter() });
|
||||
await user.click(agentBox());
|
||||
await user.keyboard("zzz");
|
||||
expect(screen.getByText("No matching agents")).toBeVisible();
|
||||
});
|
||||
|
||||
it("clears back to all agents", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onUrlUpdate = vi.fn<(event: UrlUpdateEvent) => void>();
|
||||
render(<RunsToolbar query="" onQueryChange={vi.fn()} runs={runs} />, {
|
||||
wrapper: withNuqsTestingAdapter({ searchParams: "?agent=triage", onUrlUpdate }),
|
||||
});
|
||||
expect(agentBox()).toHaveValue("triage");
|
||||
await user.click(screen.getByRole("button", { name: "Clear" }));
|
||||
expect(onUrlUpdate.mock.lastCall?.[0].searchParams.get("agent")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
@ -3,6 +3,14 @@
|
|||
import type { TraceSummary } from "../../types";
|
||||
import type { TimeWindow } from "@/components/shared/timeline/Timeline";
|
||||
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxContent,
|
||||
ComboboxEmpty,
|
||||
ComboboxInput,
|
||||
ComboboxItem,
|
||||
ComboboxList,
|
||||
} from "@/components/ui/combobox";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { useRunFilterRouting } from "../../routing";
|
||||
import { traceAgentNames } from "../../utils";
|
||||
|
|
@ -20,12 +28,7 @@ interface RunsToolbarProps {
|
|||
|
||||
export function RunsToolbar({ query, onQueryChange, runs, range, children }: RunsToolbarProps) {
|
||||
const { agent, status, setAgent, setStatus } = useRunFilterRouting();
|
||||
const agents = [
|
||||
{ value: "", label: "All agents" },
|
||||
...[...new Set([...runs.flatMap(traceAgentNames), ...(agent ? [agent] : [])])]
|
||||
.sort()
|
||||
.map((name) => ({ value: name, label: name })),
|
||||
];
|
||||
const agents = [...new Set([...runs.flatMap(traceAgentNames), ...(agent ? [agent] : [])])].sort();
|
||||
const statuses = [
|
||||
{ value: "all", label: "All status" },
|
||||
{ value: "ok", label: "No errors" },
|
||||
|
|
@ -34,18 +37,24 @@ export function RunsToolbar({ query, onQueryChange, runs, range, children }: Run
|
|||
return (
|
||||
<div className="flex shrink-0 flex-wrap items-center gap-2 border-b bg-card p-2">
|
||||
<RunSearch value={query} onChange={onQueryChange} runs={runs} range={range} />
|
||||
<Select items={agents} value={agent} onValueChange={(value) => value !== null && setAgent(value)}>
|
||||
<SelectTrigger size="sm" className="h-8 max-w-48 min-w-32 text-xs" aria-label="Filter traces by agent">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{agents.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Combobox items={agents} value={agent || null} onValueChange={(name) => setAgent(name ?? "")} autoHighlight>
|
||||
<ComboboxInput
|
||||
aria-label="Filter traces by agent"
|
||||
placeholder="All agents"
|
||||
showClear={!!agent}
|
||||
className="h-8 w-48 text-xs"
|
||||
/>
|
||||
<ComboboxContent>
|
||||
<ComboboxEmpty>No matching agents</ComboboxEmpty>
|
||||
<ComboboxList>
|
||||
{(name: string) => (
|
||||
<ComboboxItem key={name} value={name} className="text-xs">
|
||||
{name}
|
||||
</ComboboxItem>
|
||||
)}
|
||||
</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
<Select
|
||||
items={statuses}
|
||||
value={status}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue