fix(usage): show load errors in the request detail panel

The panel only checked whether data was present, so a failed query fell
through to "Request not found". That is how the ambiguous-column error
in get_request_detail looked like a missing feature.

Show a danger notice with the error message and a retry button when the
query fails, and keep "Request not found" for a lookup that returned
nothing.
This commit is contained in:
Jason committed 2026-10-03 23:28:59 +08:00
1 parent 5be4451dcd
commit a39af812e1
6 files changed
+63 -2

No files matched your search

+26 -1
View File
@@ -1,5 +1,7 @@
import type { ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { Notice } from "@/components/ui/notice";
import {
Sheet,
SheetBody,
@@ -15,6 +17,7 @@ import {
type RequestLog,
} from "@/types/usage";
import { cn } from "@/lib/utils";
import { extractErrorMessage } from "@/utils/errorUtils";
import {
SPEED_ESTIMATE_MIN_OUTPUT_TOKENS,
SPEED_MIN_OUTPUT_TOKENS,
@@ -342,7 +345,12 @@ export function RequestDetailPanel({
onClose,
}: RequestDetailPanelProps) {
const { t } = useTranslation();
const { data: request, isLoading } = useRequestDetail(requestId ?? "");
const {
data: request,
isLoading,
error,
refetch,
} = useRequestDetail(requestId ?? "");
const subtitle = request
? [
@@ -371,6 +379,23 @@ export function RequestDetailPanel({
<div className="h-[320px] animate-pulse rounded-panel bg-subtle" />
) : request ? (
<RequestDetailBody request={request} />
) : error ? (
// 查询失败和「没有这条记录」分开说:前者能重试,也要让人看到原因
<Notice
tone="danger"
title={t("usage.requestLoadFailed")}
actions={
<Button
variant="neutral"
size="compact"
onClick={() => refetch()}
>
{t("common.retry")}
</Button>
}
>
{extractErrorMessage(error) || String(error)}
</Notice>
) : (
<p className="py-10 text-center text-body text-fg-3">
{t("usage.requestNotFound")}
+1
View File
@@ -1980,6 +1980,7 @@
"withMultiplier": "with multiplier",
"requestDetail": "Request Detail",
"requestNotFound": "Request not found",
"requestLoadFailed": "Couldn't load request details",
"basicInfo": "Basic Info",
"tokenUsage": "Token Usage",
"cacheCreationCost": "Cache Creation Cost",
+1
View File
@@ -1980,6 +1980,7 @@
"withMultiplier": "倍率込み",
"requestDetail": "リクエスト詳細",
"requestNotFound": "リクエストが見つかりません",
"requestLoadFailed": "リクエスト詳細を読み込めませんでした",
"basicInfo": "基本情報",
"tokenUsage": "Token 使用量",
"cacheCreationCost": "キャッシュ作成コスト",
+1
View File
@@ -1981,6 +1981,7 @@
"withMultiplier": "含倍率",
"requestDetail": "請求詳情",
"requestNotFound": "請求未找到",
"requestLoadFailed": "請求詳情載入失敗",
"basicInfo": "基本資訊",
"tokenUsage": "Token 使用量",
"cacheCreationCost": "快取寫入成本",
+1
View File
@@ -1980,6 +1980,7 @@
"withMultiplier": "含倍率",
"requestDetail": "请求详情",
"requestNotFound": "请求未找到",
"requestLoadFailed": "请求详情加载失败",
"basicInfo": "基本信息",
"tokenUsage": "Token 使用量",
"cacheCreationCost": "缓存写入成本",
+33 -1
View File
@@ -1,4 +1,4 @@
import { render, screen } from "@testing-library/react";
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { RequestDetailPanel } from "@/components/usage/RequestDetailPanel";
import type { RequestLog } from "@/types/usage";
@@ -119,4 +119,36 @@ describe("RequestDetailPanel", () => {
).toBeInTheDocument();
expect(screen.getByText("usage.detail.sourceSession")).toBeInTheDocument();
});
it("shows the error and a retry button when loading fails", () => {
const refetch = vi.fn();
useRequestDetailMock.mockReturnValue({
data: undefined,
isLoading: false,
error: "ambiguous column name: created_at",
refetch,
});
render(<RequestDetailPanel requestId="req_1" onClose={vi.fn()} />);
expect(screen.getByText("usage.requestLoadFailed")).toBeInTheDocument();
expect(
screen.getByText("ambiguous column name: created_at"),
).toBeInTheDocument();
expect(screen.queryByText("usage.requestNotFound")).toBeNull();
fireEvent.click(screen.getByRole("button", { name: "common.retry" }));
expect(refetch).toHaveBeenCalledTimes(1);
});
it("says the request is not found only when the query returned nothing", () => {
useRequestDetailMock.mockReturnValue({
data: null,
isLoading: false,
error: null,
});
render(<RequestDetailPanel requestId="req_1" onClose={vi.fn()} />);
expect(screen.getByText("usage.requestNotFound")).toBeInTheDocument();
expect(screen.queryByText("usage.requestLoadFailed")).toBeNull();
});
});