From 22bd49e2311ec0314da6ed4ed25e685baf1f1358 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 2 Oct 2026 16:54:13 -0700 Subject: [PATCH] fix(ui): make the Lens traces refresh button always clickable (#44252) Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com> --- .../TraceView/AgentTracesSection.test.tsx | 37 +++++++++++++++++-- .../TraceView/AgentTracesSection.tsx | 7 +++- .../view_logs/TraceView/TimeRangeControls.tsx | 21 ++++++----- 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesSection.test.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesSection.test.tsx index 29acaec7f74..e90544ca0a6 100644 --- a/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesSection.test.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesSection.test.tsx @@ -7,7 +7,7 @@ import { ApiError } from "@/lib/http/client"; import { chooseSelectOption, renderWithProviders, testQueryClient } from "../../../../tests/test-utils"; import traceList from "./__fixtures__/trace_list.json"; import AgentTracesPage from "./AgentTracesPage"; -import { AgentTracesSection, filterRuns } from "./AgentTracesSection"; +import { AgentTracesSection, filterRuns, type TimeControls } from "./AgentTracesSection"; import type { TracePage, TraceSummary } from "./traceTypes"; vi.mock("../../networking", () => ({ @@ -45,7 +45,7 @@ const renderSection = () => ); // A UTC-pinned day around the fixture runs (2026-09-30 ~06:43 UTC), so they land in the same bucket in any timezone. -const renderWindowed = () => +const renderWindowed = (timeControls?: TimeControls) => renderWithProviders( endTime="2026-10-01T00:00Z" isCustomDate isLiveTail={false} + timeControls={timeControls} />, ); @@ -425,6 +426,25 @@ describe("AgentTracesSection", () => { expect(screen.queryByTestId("timeline-selection")).not.toBeInTheDocument(); expect(rowCount()).toBe(runs.length); }); + + it("clears timeline zoom when refreshed", async () => { + vi.mocked(agentTraceListCall).mockResolvedValue(traceList as TracePage); + renderWindowed({ rangeHours: 24, onRangeHoursChange: () => {}, onLiveChange: () => {} }); + await screen.findAllByTestId("agent-trace-row"); + const area = screen.getByTestId("timeline-area"); + const x = (bucket: number) => bucket * 10 + 5; + + fireEvent.pointerDown(area, { clientX: x(0), pointerId: 1 }); + fireEvent.pointerMove(area, { clientX: x(1), pointerId: 1 }); + fireEvent.pointerUp(area, { clientX: x(1), pointerId: 1 }); + expect(screen.getByTestId("timeline-selection")).toBeInTheDocument(); + expect(screen.queryAllByTestId("agent-trace-row")).toHaveLength(0); + + fireEvent.click(screen.getByRole("button", { name: "Refresh" })); + + expect(screen.queryByTestId("timeline-selection")).not.toBeInTheDocument(); + expect(screen.getAllByTestId("agent-trace-row")).toHaveLength(runs.length); + }); }); describe("AgentTracesPage", () => { @@ -453,7 +473,18 @@ describe("AgentTracesPage", () => { expect(live).toHaveAttribute("aria-pressed", "true"); fireEvent.click(live); expect(live).toHaveAttribute("aria-pressed", "false"); - expect(screen.getByRole("button", { name: "Reset zoom" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "Refresh" })).toBeEnabled(); + }); + + it("refreshes the trace list", async () => { + vi.mocked(agentTraceListCall).mockResolvedValue(traceList as TracePage); + renderWithProviders(); + await screen.findByTestId("runs-table"); + + const callsBeforeRefresh = vi.mocked(agentTraceListCall).mock.calls.length; + fireEvent.click(screen.getByRole("button", { name: "Refresh" })); + + await waitFor(() => expect(vi.mocked(agentTraceListCall).mock.calls.length).toBeGreaterThan(callsBeforeRefresh)); }); it("keeps the time controls on an empty range the user picked, instead of showing onboarding", async () => { diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesSection.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesSection.tsx index 4ec8fe7e0ec..1b25ca19051 100644 --- a/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesSection.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesSection.tsx @@ -211,8 +211,11 @@ export function AgentTracesSection({ onRangeHoursChange={(hours) => changeRange(hours, timeControls.onRangeHoursChange)} live={isLiveTail} onLiveChange={timeControls.onLiveChange} - zoomed={zoom !== null} - onResetZoom={() => setZoom(null)} + onRefresh={() => { + setZoom(null); + checkTraces(); + }} + refreshing={traces.isFetching} /> )} diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/TimeRangeControls.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/TimeRangeControls.tsx index 0d3cf77a1e5..03d950a7725 100644 --- a/ui/litellm-dashboard/src/components/view_logs/TraceView/TimeRangeControls.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/TimeRangeControls.tsx @@ -35,30 +35,31 @@ interface TimeRangeControlsProps { onRangeHoursChange: (hours: number) => void; live: boolean; onLiveChange: (live: boolean) => void; - zoomed: boolean; - onResetZoom: () => void; + onRefresh: () => void; + refreshing: boolean; } -/** Joined control group: reset zoom, the actual time range (opens presets), and Live. */ +/** Joined control group: refresh, the actual time range (opens presets), and Live. */ export function TimeRangeControls({ range, rangeHours, onRangeHoursChange, live, onLiveChange, - zoomed, - onResetZoom, + onRefresh, + refreshing, }: TimeRangeControlsProps) { return (