fix(lens): default to traces and restore closed span details (#44260)

This commit is contained in:
moe-berri 2026-10-02 16:05:00 -07:00 • committed by GitHub
parent 04bc354525
commit aef0a53837
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 35 additions and 5 deletions

View file

@ -18,12 +18,11 @@ vi.mock("./_components/LensView", () => ({
describe("Lens navigation", () => {
beforeEach(() => auth.mockReturnValue({ accessToken: "test-token", userRole: "Admin", isViewOnly: false }));
it("opens investigations and pauses trace polling when returning from traces", async () => {
it("opens traces by default and pauses polling while viewing investigations", async () => {
const user = userEvent.setup();
const onUrlUpdate = vi.fn();
renderWithProviders(<LensPage />, { onUrlUpdate });
expect(screen.getByRole("tab", { name: "Investigations" })).toHaveAttribute("aria-selected", "true");
await user.click(screen.getByRole("tab", { name: "Traces" }));
expect(screen.getByRole("tab", { name: "Traces" })).toHaveAttribute("aria-selected", "true");
expect(screen.getByText("Trace polling active")).toBeVisible();
await user.click(screen.getByRole("tab", { name: "Investigations" }));
expect(screen.getByText("Manage investigations")).toBeVisible();
@ -39,6 +38,12 @@ describe("Lens navigation", () => {
expect(screen.getByText("Manage investigations")).toBeVisible();
});
it("honors an explicit traces tab even when a saved investigation is in the URL", () => {
renderWithProviders(<LensPage />, { searchParams: "?tab=traces&lens=saved-lens" });
expect(screen.getByRole("tab", { name: "Traces" })).toHaveAttribute("aria-selected", "true");
expect(screen.getByText("Trace polling active")).toBeVisible();
});
it.each(["Internal User", "Internal Viewer", "Org Admin"])(
"preserves trace access without granting investigations to %s",
async (userRole) => {

View file

@ -15,7 +15,7 @@ export default function LensPage() {
parseAsStringLiteral(["traces", "investigations"]).withOptions({ history: "push" }),
);
const [lensId] = useQueryState("lens", parseAsString);
const defaultTab = isProxyAdminTierRole(userRole ?? "") || lensId ? "investigations" : "traces";
const defaultTab = lensId ? "investigations" : "traces";
const activeTab = tab ?? defaultTab;
if (!accessToken) return null;
return (

View file

@ -1,8 +1,9 @@
"use client";
import { BarChart3, Clock, Coins, ListTree, MoreHorizontal, Timer } from "lucide-react";
import { BarChart3, Clock, Coins, ListTree, MoreHorizontal, PanelRightOpen, Timer } from "lucide-react";
import { useEffect, useMemo, useRef } from "react";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import { cn } from "@/lib/cva.config";
@ -24,6 +25,7 @@ interface SpanTreeProps {
onToggleSpan: (id: string) => void;
onToggleGroup: (id: string) => void;
onLoadMore: (groupId: string) => void;
onOpenDetails?: () => void;
/** Inside the side drawer J/K switch runs, so spans move with the arrow keys. */
embedded?: boolean;
}
@ -308,6 +310,7 @@ export function SpanTree({
onToggleSpan,
onToggleGroup,
onLoadMore,
onOpenDetails,
embedded = false,
}: SpanTreeProps) {
const scrollRef = useRef<HTMLDivElement>(null);
@ -333,6 +336,12 @@ export function SpanTree({
aria-label="Hide framework spans"
/>
</label>
{onOpenDetails && (
<Button variant="ghost" size="sm" onClick={onOpenDetails} className="ml-3 shrink-0 text-xs text-trace-text-2">
<PanelRightOpen className="size-4" />
Show details
</Button>
)}
</div>
<div ref={scrollRef} className="min-h-0 flex-1 overflow-auto pb-4">
<Summary summary={summary} />

View file

@ -135,6 +135,21 @@ describe("RunView", () => {
expect(screen.queryByTestId("detail-pane")).not.toBeInTheDocument();
});
it.each(["button", "Escape"])("reopens the selected step after closing details with %s", async (method) => {
const user = userEvent.setup();
renderRun(research);
await screen.findByTestId("detail-pane");
await user.keyboard("j");
const selectedId = screen.getByTestId("detail-pane").getAttribute("data-row-id");
expect(selectedId).not.toBe(rootSpanId(research));
if (method === "button") await user.click(screen.getByRole("button", { name: "close detail" }));
else await user.keyboard("{Escape}");
expect(screen.queryByTestId("detail-pane")).not.toBeInTheDocument();
await user.click(screen.getByRole("button", { name: "Show details" }));
expect(screen.getByTestId("detail-pane")).toHaveAttribute("data-row-id", selectedId);
expect(screen.queryByRole("button", { name: "Show details" })).not.toBeInTheDocument();
});
it("keeps a way back to the runs table when a run fails to load", async () => {
const user = userEvent.setup();
const onBack = vi.fn();

View file

@ -253,6 +253,7 @@ function RunBody({ trace, accessToken, initialSpanId, embedded }: RunBodyProps)
onToggleSpan={toggleSpan}
onToggleGroup={toggleGroup}
onLoadMore={loadMore}
onOpenDetails={detailOpen ? undefined : () => setDetailOpen(true)}
embedded={embedded}
/>
{detailOpen && (