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>
This commit is contained in:
devin-ai-integration[bot] 2026-10-02 16:54:13 -07:00 • committed by GitHub
parent a292fd409f
commit 22bd49e231
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 50 additions and 15 deletions

View file

@ -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(
<AgentTracesSection
accessToken="sk-test"
@ -54,6 +54,7 @@ const renderWindowed = () =>
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(<AgentTracesPage accessToken="sk-test" />);
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 () => {

View file

@ -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}
/>
)}
</RunsToolbar>

View file

@ -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 (
<div className="flex items-center gap-1.5">
<button
type="button"
onClick={onResetZoom}
disabled={!zoomed}
aria-label="Reset zoom"
className="inline-flex size-7 items-center justify-center rounded-md border border-border text-info hover:bg-info/10 disabled:text-muted-foreground disabled:opacity-40 disabled:hover:bg-transparent"
onClick={onRefresh}
aria-label="Refresh"
title="Refresh"
aria-busy={refreshing}
className="inline-flex size-7 items-center justify-center rounded-md border border-border text-muted-foreground hover:text-foreground hover:bg-muted/60"
>
<RotateCcw className="size-3.5" />
<RotateCcw className={cn("size-3.5", refreshing && "animate-spin")} />
</button>
<div className="flex items-center divide-x divide-border overflow-hidden rounded-md border border-border bg-card">
<DropdownMenu>