From 7aba6e23619a4e1b367a2dc1751ce52e9bb1420e Mon Sep 17 00:00:00 2001
From: Priyanshu-u07
Date: Mon, 26 Oct 2026 18:38:09 +0530
Subject: [PATCH] Show and search request IDs in the Insights logs
Signed-off-by: Priyanshu-u07
---
apps/dashboard/src/pages/Insights.test.tsx | 92 +++++++++++++++++++
apps/dashboard/src/pages/Insights.tsx | 67 ++++++++++----
.../dashboard/src/services/insightsService.ts | 2 +
3 files changed, 144 insertions(+), 17 deletions(-)
create mode 100644 apps/dashboard/src/pages/Insights.test.tsx
diff --git a/apps/dashboard/src/pages/Insights.test.tsx b/apps/dashboard/src/pages/Insights.test.tsx
new file mode 100644
index 00000000..26cd1d4c
--- /dev/null
+++ b/apps/dashboard/src/pages/Insights.test.tsx
@@ -0,0 +1,92 @@
+import { render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { MemoryRouter } from "react-router-dom";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+
+vi.mock("@/services/insightsService", () => ({
+ insightsService: {
+ getSummary: vi.fn(),
+ getTimeseries: vi.fn(),
+ getLogs: vi.fn(),
+ getFilters: vi.fn(),
+ getTopIps: vi.fn(),
+ getTopModels: vi.fn(),
+ },
+}));
+
+// The charts are lazy and need a real layout; they are not under test here.
+vi.mock("@/components/insights/InsightsCharts", () => ({
+ RequestVolumeChart: () => null,
+ TokenUsageChart: () => null,
+ ModelDistributionChart: () => null,
+ TrafficByIpChart: () => null,
+}));
+
+import { insightsService } from "@/services/insightsService";
+import Insights from "./Insights";
+
+const service = insightsService as unknown as Record>;
+const REQUEST_ID = "3f9a1c0e-7b2d-4a6f-8e1c-3b5a7d9f0e2c";
+
+function log(requestId: string | null) {
+ return {
+ id: `log-${requestId}`, deployment_id: "dep-12345678", user_id: "apikey:k1", model: "qwen",
+ ip_address: "10.0.0.1", request_id: requestId, request_payload: null, latency_ms: 120,
+ ttft_ms: null, tokens_per_second: null, prompt_tokens: 10, completion_tokens: 5,
+ total_tokens: 15, status_code: 200, error_message: null, is_streaming: false,
+ applied_policies: null, created_at: "2026-10-26T10:00:00Z",
+ };
+}
+
+function renderPage(url = "/dashboard/insights") {
+ const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
+ return render(
+
+
+
+
+ ,
+ );
+}
+
+beforeEach(() => {
+ globalThis.ResizeObserver ??= class { observe() {} unobserve() {} disconnect() {} } as never;
+ const totals = { requests: 2, successful_requests: 2, failed_requests: 0, success_rate: 100,
+ prompt_tokens: 20, completion_tokens: 10, total_tokens: 30 };
+ service.getSummary.mockResolvedValue({ totals, latency_ms: { avg: 120, samples: 2 },
+ ttft_ms: { avg: 0, samples: 0 }, throughput: { requests_per_minute: 1, tokens_per_second: 1, avg_tokens_per_second: 1 } });
+ service.getTimeseries.mockResolvedValue({ granularity: "day", buckets: [] });
+ service.getFilters.mockResolvedValue({ deployments: [], models: [], ip_addresses: [], status_options: [], deployment_types: [] });
+ service.getTopIps.mockResolvedValue({ items: [] });
+ service.getTopModels.mockResolvedValue({ items: [] });
+ service.getLogs.mockResolvedValue({ items: [log(REQUEST_ID), log(null)], pagination: { limit: 25, offset: 0, total: 2 } });
+});
+
+afterEach(() => vi.clearAllMocks());
+
+describe("Insights request ID", () => {
+ it("shows each log's request ID, shortened, with the full ID on hover", async () => {
+ renderPage();
+ const cell = await screen.findByTitle(REQUEST_ID);
+ expect(cell).toHaveTextContent("3f9a1c0e-7b2…");
+ expect(screen.getByRole("columnheader", { name: "Request ID" })).toBeInTheDocument();
+ });
+
+ it("searches the logs by request ID, and only the logs", async () => {
+ renderPage();
+ await screen.findByTitle(REQUEST_ID);
+ await userEvent.type(screen.getByLabelText("Find a request by its ID"), "trace-42");
+ await waitFor(() =>
+ expect(service.getLogs).toHaveBeenLastCalledWith(expect.objectContaining({ request_id: "trace-42", offset: 0 })));
+ for (const call of service.getSummary.mock.calls) expect(call[0]).not.toHaveProperty("request_id");
+ });
+
+ it("opens with the search from the link, and says when nothing matches", async () => {
+ service.getLogs.mockResolvedValue({ items: [], pagination: { limit: 25, offset: 0, total: 0 } });
+ renderPage("/dashboard/insights?requestId=trace-404");
+ expect(await screen.findByText("No log with this request ID in the selected time range.")).toBeInTheDocument();
+ expect(screen.getByLabelText("Find a request by its ID")).toHaveValue("trace-404");
+ expect(service.getLogs).toHaveBeenCalledWith(expect.objectContaining({ request_id: "trace-404" }));
+ });
+});
diff --git a/apps/dashboard/src/pages/Insights.tsx b/apps/dashboard/src/pages/Insights.tsx
index afb064dd..1bed027c 100644
--- a/apps/dashboard/src/pages/Insights.tsx
+++ b/apps/dashboard/src/pages/Insights.tsx
@@ -72,6 +72,7 @@ type State = {
customEnd: string;
deploymentId: string;
ipAddress: string;
+ requestId: string;
status: InsightsStatus;
page: number;
pageSize: number;
@@ -89,6 +90,7 @@ const createInitialState = (defaultStart: Date, now: Date): State => ({
customEnd: toLocalDateTimeInputValue(now),
deploymentId: "",
ipAddress: "",
+ requestId: "",
status: "all",
page: 1,
pageSize: DEFAULT_PAGE_SIZE,
@@ -119,6 +121,7 @@ function createInitialStateFromParams(searchParams: URLSearchParams, defaultStar
customEnd: searchParams.get("customEnd") || baseState.customEnd,
deploymentId: searchParams.get("deploymentId") || "",
ipAddress: searchParams.get("ipAddress") || "",
+ requestId: searchParams.get("requestId") || "",
status: status ? (status as InsightsStatus) : baseState.status,
page,
pageSize: PAGE_SIZE_OPTIONS.includes(parsedPageSize) ? parsedPageSize : DEFAULT_PAGE_SIZE,
@@ -132,7 +135,7 @@ function reducer(state: State, action: Action): State {
case 'SET_DEPLOYMENT_TYPE':
return { ...state, deploymentType: action.payload, deploymentId: "", page: 1 };
case 'CLEAR_FILTERS':
- return { ...state, deploymentId: "", ipAddress: "", status: "all", page: 1 };
+ return { ...state, deploymentId: "", ipAddress: "", requestId: "", status: "all", page: 1 };
default:
return state;
}
@@ -150,7 +153,7 @@ export default function Insights() {
);
const {
deploymentType, timePreset, customStart, customEnd,
- deploymentId, ipAddress, status, page, pageSize
+ deploymentId, ipAddress, requestId, status, page, pageSize
} = state;
useEffect(() => {
@@ -170,6 +173,7 @@ export default function Insights() {
setOrDelete("customEnd", timePreset === "custom" ? customEnd : null);
setOrDelete("deploymentId", deploymentId || null);
setOrDelete("ipAddress", ipAddress.trim() || null);
+ setOrDelete("requestId", requestId.trim() || null);
setOrDelete("status", status === "all" ? null : status);
setOrDelete("page", page > 1 ? String(page) : null);
setOrDelete("pageSize", pageSize !== DEFAULT_PAGE_SIZE ? String(pageSize) : null);
@@ -177,9 +181,10 @@ export default function Insights() {
if (next.toString() !== searchParams.toString()) {
setSearchParams(next, { replace: true });
}
- }, [deploymentType, timePreset, customStart, customEnd, deploymentId, ipAddress, status, page, pageSize, searchParams, setSearchParams]);
+ }, [deploymentType, timePreset, customStart, customEnd, deploymentId, ipAddress, requestId, status, page, pageSize, searchParams, setSearchParams]);
const debouncedIpAddress = useDebouncedValue(ipAddress.trim(), IP_DEBOUNCE_DELAY);
+ const debouncedRequestId = useDebouncedValue(requestId.trim(), IP_DEBOUNCE_DELAY);
const range = useMemo(() => {
const end = new Date();
@@ -307,14 +312,17 @@ export default function Insights() {
baseParams.end_time,
deploymentId,
debouncedIpAddress,
+ debouncedRequestId,
status,
page,
pageSize,
deploymentType,
],
+ // Only the logs list can be searched by request ID; the totals and charts cannot.
queryFn: () =>
insightsService.getLogs({
...baseParams,
+ request_id: debouncedRequestId || undefined,
limit: pageSize,
offset: (page - 1) * pageSize,
}),
@@ -395,7 +403,7 @@ export default function Insights() {
logsQuery.isLoading;
const hasNoData = (summary?.totals.requests ?? 0) === 0 && !isInitialLoading;
- const hasActiveFilters = deploymentId !== "" || ipAddress !== "" || status !== "all";
+ const hasActiveFilters = deploymentId !== "" || ipAddress !== "" || requestId !== "" || status !== "all";
const clearFilters = useCallback(() => {
dispatch({ type: 'CLEAR_FILTERS' });
@@ -403,9 +411,10 @@ export default function Insights() {
const exportLogsToCSV = useCallback(() => {
if (!logs?.items.length) return;
- const headers = ["Timestamp", "Deployment ID", "Model", "IP Address", "Prompt Tokens", "Completion Tokens", "Total Tokens", "Latency (ms)", "Status"];
+ const headers = ["Timestamp", "Request ID", "Deployment ID", "Model", "IP Address", "Prompt Tokens", "Completion Tokens", "Total Tokens", "Latency (ms)", "Status"];
const rows = logs.items.map((log) => [
log.created_at,
+ log.request_id || "",
log.deployment_id,
log.model,
log.ip_address || "",
@@ -836,16 +845,33 @@ export default function Insights() {
}
- {logs?.items.length ? (
-
- ) : null}
+
+ {
+ dispatch({ type: 'SET_FIELD', field: 'requestId', value: e.target.value });
+ dispatch({ type: 'SET_FIELD', field: 'page', value: 1 });
+ }}
+ placeholder="Find request ID"
+ maxLength={128}
+ autoComplete="off"
+ spellCheck={false}
+ className="h-8 w-56 rounded-md border bg-background px-3 text-xs font-mono"
+ />
+ {logs?.items.length ? (
+
+ ) : null}
+
@@ -853,6 +879,7 @@ export default function Insights() {
| Timestamp |
+ Request ID |
Deployment |
Model |
IP |
@@ -867,6 +894,10 @@ export default function Insights() {
{new Date(log.created_at).toLocaleString()}
|
+
+ {!log.request_id ? "-"
+ : log.request_id.length > 14 ? `${log.request_id.slice(0, 12)}…` : log.request_id}
+ |
{String(log.deployment_id).slice(0, 8)}…
|
@@ -899,8 +930,10 @@ export default function Insights() {
))}
{!logsQuery.isLoading && (logs?.items.length || 0) === 0 && (
- |
- No logs found for the selected filters.
+ |
+ {debouncedRequestId
+ ? "No log with this request ID in the selected time range."
+ : "No logs found for the selected filters."}
|
)}
diff --git a/apps/dashboard/src/services/insightsService.ts b/apps/dashboard/src/services/insightsService.ts
index 0502c262..1fe7537f 100644
--- a/apps/dashboard/src/services/insightsService.ts
+++ b/apps/dashboard/src/services/insightsService.ts
@@ -66,6 +66,7 @@ export interface InferenceLogItem {
user_id: string;
model: string;
ip_address: string | null;
+ request_id: string | null;
request_payload: Record | null;
latency_ms: number | null;
ttft_ms: number | null;
@@ -130,6 +131,7 @@ export interface InsightsTopModelsResponse {
export interface InsightsLogsQueryParams extends InsightsQueryParams {
limit?: number;
offset?: number;
+ request_id?: string;
}
export interface InsightsTimeseriesQueryParams extends InsightsQueryParams {