Skip to content

Fix/logs provider badge overflow#581

Merged
ding113 merged 8 commits intoding113:devfrom
YangQing-Lin:fix/logs-provider-badge-overflow
Jan 10, 2026
Merged

Fix/logs provider badge overflow#581
ding113 merged 8 commits intoding113:devfrom
YangQing-Lin:fix/logs-provider-badge-overflow

Conversation

@YangQing-Lin
Copy link
Collaborator

@YangQing-Lin YangQing-Lin commented Jan 10, 2026

PR: Fix logs provider badge overflow + hide invalid cost multiplier

基本信息

  • Target branch: dev
  • Branch: fix/logs-provider-badge-overflow
  • 页面/范围:/zh-CN/dashboard/logs(Dashboard -> Logs)

背景与问题

  1. Provider 单元格在“供应商名称很长”且出现“次数 Badge + Info 图标”时,会把右侧倍率 Badge 挤出单元格,导致越界/遮挡相邻列。

修复前截图:

image

修复后截图:

image
  1. costMultiplier 异常值(如 NaN/Infinity/""/null/undefined)会导致 UI 展示 ×NaN/×Infinity(或详情中出现 multiplier 行),应对齐参考行为:仅在有限数值且不等于 1 时展示倍率。

变更概览

1) 修复 Provider 单元格布局溢出

  • 在 Provider 行的 flex 结构中补齐可收缩/可截断的关键 class:min-w-0flex-1overflow-hidden,并保持倍率 Badge shrink-0,避免被挤出或覆盖相邻列。
  • 相关文件:
    • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx:289
    • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx:75

2) 隐藏非法倍率(避免 NaN/Infinity 渲染)

  • VirtualizedLogsTable / UsageLogsTable / ErrorDetailsDialog:
    • costMultiplier 统一使用 Number() 转换后用 Number.isFinite(...) 校验;
    • 仅当 multiplier !== 1 时渲染倍率(Badge/详情行)。
  • 相关文件:
    • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx:301
    • src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx:96
    • src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx:539

测试与验证

必跑清单(CLAUDE.md)

  • bun run build
  • bun run lint
  • bun run lint:fix
  • bun run typecheck
  • bun run test ✅(Vitest 全量通过)

额外验证

  • 使用 .env.local 重新跑全量测试:bun --env-file=.env.local run test
  • 组件级回归覆盖(logs 相关组件):
    • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.test.tsx
    • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx
    • src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx
    • src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx

手工验收建议(视觉回归)

  1. 打开 /zh-CN/dashboard/logs,构造/筛选一条记录:
    • Provider 名称超长
    • providerChain 出现多次请求(展示次数 Badge + InfoIcon)
  2. 观察 Provider 单元格:倍率 Badge 不越界、不遮挡相邻列;名称可正常 truncate。
  3. 构造倍率异常:costMultiplier = "NaN" | "Infinity" | "" | null | undefined
  4. 观察:Provider 列与 ErrorDetailsDialog 里均不出现 ×NaN/×Infinity,且详情中不出现 multiplier 行。

风险与回滚

风险

  • 纯样式/布局调整:不同浏览器/字体下的截断细节可能略有差异(需视觉确认)。
  • overflow-hidden 可能裁切某些 focus outline(建议键盘聚焦触发器验证)。

回滚

  • 回滚该 PR 的提交即可恢复原行为(无数据迁移、无 schema 变更)。

Commit 列表

  • 59a06cb5 fix: prevent provider badge overflow in logs table
  • 753a5dc2 fix: make provider chain trigger shrinkable
  • 46eacda6 fix: hide invalid cost multiplier badge
  • 132f37d5 fix: avoid invalid multiplier in usage logs
  • edf23761 fix: hide invalid multiplier in error details
  • d8d0a897 test: add provider chain popover layout regression
  • 0be62a59 test: cover invalid multiplier rendering branches
  • 179db42f test: cover empty and undefined cost multiplier

PR Checklist(提交前自检)

  • 目标分支为 dev
  • 无新增硬编码用户可见文案(i18n 保持一致)
  • Provider 列在长名称 + 次数 Badge + InfoIcon 场景下不越界
  • costMultiplier 非有限值不渲染倍率(列表与详情一致)
  • bun run build/lint/lint:fix/typecheck/test 全绿

Greptile Overview

Greptile Summary

This PR fixes two UI issues in the logs table:

Changes Overview

1. Layout Overflow Fix

  • Added flex-1, min-w-0, w-full, and overflow-hidden classes to provider column containers
  • Wrapped ProviderChainPopover in a flex container to enable proper truncation
  • Modified button trigger classes from max-w-full shrink to w-full min-w-0 for better flex behavior
  • These changes prevent the cost multiplier badge from overflowing/overlapping adjacent columns when provider names are long

2. Invalid Multiplier Handling

  • Replaced parseFloat(String(...)) with Number() for type conversion
  • Added comprehensive validation: checks for empty string, null, and uses Number.isFinite() to filter NaN/Infinity
  • Only displays multiplier badge/row when value is finite and not equal to 1
  • Consistently applied across VirtualizedLogsTable, UsageLogsTable, and ErrorDetailsDialog

Implementation Quality

Strengths:

  • Well-tested with comprehensive coverage for edge cases (NaN, Infinity, null, undefined, empty string)
  • Layout fixes follow established patterns in the codebase (flex-1 min-w-0 convention)
  • Changes are minimal and targeted to specific problem areas
  • Consistent implementation across all three table components

Minor Concern:

  • Edge case where costMultiplier = "0" will display ×0.00 badge, which may or may not be intended behavior. Consider if this should be hidden or displayed differently.

Testing

All existing tests pass, and new tests specifically cover:

  • Layout regression (min-w-0, w-full, flex-1 classes)
  • Invalid multiplier values (empty, undefined, NaN, Infinity)
  • Valid multiplier rendering (0.2x displays correctly)

Confidence Score: 4/5

  • This PR is safe to merge with one minor consideration for edge case handling
  • Score of 4 reflects high-quality implementation with comprehensive testing, but reduced by 1 point due to the unaddressed edge case where costMultiplier=0 may display unexpectedly. The layout fixes are solid, validation logic is sound, and test coverage is thorough for the main scenarios.
  • Pay attention to virtualized-logs-table.tsx for the costMultiplier=0 edge case consideration

Important Files Changed

File Analysis

Filename Score Overview
src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx 4/5 Layout overflow fix with flex-1/min-w-0 classes and improved multiplier validation, but edge case where multiplier=0 may show ×0.00 badge
src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx 5/5 Clean layout fixes adding w-full and flex-1 to prevent badge overflow, changes are minimal and well-targeted
src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx 5/5 Extracted multiplier calculation logic outside IIFE, improved validation checks for null/empty before Number conversion
src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx 5/5 Improved multiplier validation with proper checks for empty/null/non-finite values before rendering

Sequence Diagram

sequenceDiagram
    participant User
    participant LogTable as VirtualizedLogsTable/<br/>UsageLogsTable
    participant Popover as ProviderChainPopover
    participant Dialog as ErrorDetailsDialog

    User->>LogTable: View logs with long provider names
    
    Note over LogTable: Calculate multiplier validation
    LogTable->>LogTable: successfulProvider?.costMultiplier ?? log.costMultiplier
    LogTable->>LogTable: Check: !== "" && != null
    LogTable->>LogTable: Convert: Number(actualCostMultiplier)
    LogTable->>LogTable: Validate: isFinite() && !== 1
    
    alt hasCostBadge = true
        LogTable->>Popover: Pass hasCostBadge=true
        Note over Popover: Adjust max-width<br/>for badge space
        Popover->>LogTable: Render with flex-1 min-w-0
        LogTable->>User: Display truncated name + Badge
    else hasCostBadge = false
        LogTable->>Popover: Pass hasCostBadge=false
        Popover->>LogTable: Render with wider max-width
        LogTable->>User: Display truncated name only
    end
    
    User->>Dialog: Click status code for details
    Dialog->>Dialog: Validate costMultiplier
    alt Valid and !== 1
        Dialog->>User: Show multiplier row in billing details
    else Invalid/null/empty/1
        Dialog->>User: Hide multiplier row
    end
Loading

@coderabbitai
Copy link

coderabbitai bot commented Jan 10, 2026

📝 Walkthrough

概览

此拉取请求为日志仪表板的错误详情、提供者链和使用日志表组件增加了全面的单元测试覆盖,并重构了成本乘数显示逻辑和布局宽度处理,统一了跨多个组件的乘数计算。

变更

队列 / 文件 变更摘要
错误详情对话框测试
src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx
新增375行测试覆盖,包括异常状态码映射、过滤提供者、提供者链时间线、JSON错误消息格式化、乘数行渲染逻辑验证
错误详情对话框组件
src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx
将成本乘数显示逻辑从条件渲染重构为内联保护函数,改进数值处理和格式化
提供者链弹出框测试
src/app/[locale]/dashboard/logs/_components/provider-chain-popover.test.tsx
新增149行测试,验证单/多请求场景下的布局宽度约束和DOM结构
提供者链弹出框组件
src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx
调整布局宽度处理,单请求路径添加w-full,多请求路径优化flex布局和min-w-0约束
使用日志表测试
src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx
新增184行测试,涵盖乘数徽章渲染、预热跳过/已阻止标签、模型重定向和分页交互
使用日志表组件
src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx
引入统一的提供者成本乘数计算路径,消除重复的内联逻辑,整合到预计算的hasCostBadge和乘数值
虚拟化日志表测试
src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx
新增280行全面测试套件,验证加载/错误/空状态、乘数徽章逻辑、滚动按钮、阻止徽章和提供者总结渲染
虚拟化日志表组件
src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx
在提供者块容器添加宽度和溢出处理,改进乘数计算逻辑,更新徽章样式条件和文本显示格式

预计代码审查工作量

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 标题与 PR 变更内容相关,准确地反映了主要改动:修复 logs 表中 Provider 徽章溢出的问题。
Description check ✅ Passed 描述与变更内容完全相关,详细阐述了两个主要问题(布局溢出和非法倍率渲染)、修复方案、测试验证和风险说明。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @YangQing-Lin, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求旨在解决日志仪表板中存在的两个主要用户界面问题。首先,它修复了当供应商名称较长且同时显示请求次数徽章和信息图标时,Provider 列中成本倍率徽章溢出单元格并遮挡相邻列的布局问题。其次,它解决了 'costMultiplier' 字段在遇到 'NaN'、'Infinity'、空字符串、'null' 或 'undefined' 等无效值时,UI 会错误地显示 '×NaN' 或 '×Infinity' 的问题。通过引入更健壮的数字校验和布局调整,此更改提升了日志界面的视觉一致性和数据准确性,确保了用户体验的优化。

Highlights

  • 修复日志表格中 Provider 列的布局溢出问题: 通过在 Provider 单元格的 flex 结构中添加 'min-w-0'、'flex-1' 和 'overflow-hidden' 等关键 CSS 类,并确保倍率徽章 'shrink-0',解决了当供应商名称过长或包含多个徽章时,倍率徽章被挤出或遮挡相邻列的问题。
  • 隐藏无效的成本倍率显示: 统一对 'costMultiplier' 值进行 'Number()' 转换和 'Number.isFinite()' 校验。现在,只有当倍率是有限数值且不等于 1 时,才会在日志表格和错误详情对话框中显示倍率徽章或详情行,避免了 '×NaN' 或 '×Infinity' 等异常值的渲染。
  • 新增全面的单元测试: 为 'ErrorDetailsDialog'、'ProviderChainPopover'、'UsageLogsTable' 和 'VirtualizedLogsTable' 增加了新的测试用例,覆盖了布局回归、无效倍率渲染、各种状态显示以及交互行为,确保了修复的稳定性和正确性。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions bot added bug Something isn't working area:UI labels Jan 10, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

这个 PR 做得很好,成功解决了两个主要问题:日志表格中 Provider 徽章的溢出问题,以及无效 costMultiplier 值的显示问题。

  1. 布局修复:在 virtualized-logs-table.tsxprovider-chain-popover.tsx 中对 Flexbox 属性(如 flex-1, min-w-0, overflow-hidden)的运用非常到位,有效地解决了文本截断和 UI 溢出的问题。
  2. 逻辑健壮性:在 error-details-dialog.tsx, usage-logs-table.tsxvirtualized-logs-table.tsx 中,对 costMultiplier 的处理逻辑变得更加严谨,通过 Number.isFinite 等检查,正确地排除了 NaNInfinity 等无效值。
  3. 测试覆盖:为布局变更和倍率逻辑都添加了全面的测试用例,这对于保证代码库的稳定性非常有帮助。

整体来看,这是一次高质量的提交,代码清晰,目标明确。我有一些关于代码可读性和复用性的小建议,请查看具体的评论。

Comment on lines +539 to +552
{(() => {
if (costMultiplier === "" || costMultiplier == null) return null;
const multiplier = Number(costMultiplier);
if (!Number.isFinite(multiplier) || multiplier === 1) return null;

return (
<div className="flex justify-between">
<span className="text-muted-foreground">
{t("logs.billingDetails.multiplier")}:
</span>
<span className="font-mono">{multiplier.toFixed(2)}x</span>
</div>
);
})()}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

您好,这部分对 costMultiplier 的校验逻辑非常棒,很严谨。

为了与本次 PR 中其他文件(如 usage-logs-table.tsx)的风格保持一致,并提高代码可读性,建议将这部分逻辑从 IIFE (立即调用函数表达式) 中提取出来。

例如,可以在组件函数体顶部(返回 JSX 之前)定义 multipliershowMultiplier 变量,然后在 JSX 中直接使用它们进行条件渲染。这样可以让 JSX 结构更简洁清晰,也更便于维护。

Comment on lines +109 to +114
const multiplier =
actualCostMultiplier === "" || actualCostMultiplier == null
? null
: Number(actualCostMultiplier);
const hasCostBadge =
multiplier != null && Number.isFinite(multiplier) && multiplier !== 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

这套用于校验 costMultiplier 的逻辑非常健壮,并且在 error-details-dialog.tsxvirtualized-logs-table.tsx 中也得到了应用。

为了遵循 DRY (Don't Repeat Yourself) 原则并提高代码的可维护性,可以考虑将这部分逻辑提取到一个共享的工具函数中,例如 getValidMultiplier(costMultiplier)

这个函数可以接收 costMultiplier 作为参数,并返回一个有效的、不为 1 的 number 或者 null。这样,所有需要此逻辑的组件都可以复用它,使得代码更整洁,未来修改也只需在一处进行。

例如:

// In a utils file
export function getValidMultiplier(costMultiplier: string | number | null | undefined): number | null {
  if (costMultiplier === "" || costMultiplier == null) {
    return null;
  }
  const multiplier = Number(costMultiplier);
  if (!Number.isFinite(multiplier) || multiplier === 1) {
    return null;
  }
  return multiplier;
}

// In the component
const multiplier = getValidMultiplier(actualCostMultiplier);
const hasCostBadge = multiplier !== null;

@github-actions github-actions bot added the size/XL Extra Large PR (> 1000 lines) label Jan 10, 2026
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx:
- Around line 516-562: Wrap the test "scrolls to model redirect section when
scrollToRedirect is true" body in a try/finally so that
Element.prototype.scrollIntoView is always restored and vi.useRealTimers()
always called; capture the originalScrollIntoView before overriding, set
Element.prototype.scrollIntoView to scrollIntoViewMock for the test, run the
assertions, and in finally restore Element.prototype.scrollIntoView =
originalScrollIntoView and call vi.useRealTimers(), also ensure
container.remove() happens in the finally block to avoid leaking DOM between
tests.

In @src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx:
- Around line 539-552: The rendering shows "0.00x" for inputs like "   " because
costMultiplier is not trimmed before numeric conversion; update the IIFE around
costMultiplier to first compute const trimmed = costMultiplier?.trim(); treat
trimmed === "" or trimmed == null as no multiplier, then parse const multiplier
= Number(trimmed) and keep the existing checks (Number.isFinite(multiplier) &&
multiplier !== 1) before rendering; reference costMultiplier and multiplier in
the change.
🧹 Nitpick comments (5)
src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx (1)

81-116: “不渲染倍率”的断言略宽松,建议补一个更直接的“倍率徽标不存在”断言以防误报

当前主要通过 not.toContain("×NaN"/"×Infinity") 来侧证;如果未来渲染了其它倍率文本但不包含这些片段,可能漏检。可以考虑额外断言“×”徽标整体不存在(例如定位 badge 容器/特定 class/data-slot,或断言不包含 0.20x 这类格式)。

src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx (1)

345-345: 倍率显示字符与 usage-logs-table.tsx 不一致

此处使用 x{multiplier.toFixed(2)}(ASCII 字母 x),而 usage-logs-table.tsx 第 197 行使用 ×{multiplier.toFixed(2)}(乘号 U+00D7)。建议统一使用相同的字符以保持一致性。

建议修复
-                                    x{multiplier.toFixed(2)}
+                                    ×{multiplier.toFixed(2)}

或者在 usage-logs-table.tsx 中改为使用 x

src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx (1)

182-194: 建议补充 multiplier === 1 的测试用例

当前测试覆盖了无效值和有效非 1 值的情况,但缺少对 multiplier === 1 的测试。根据业务逻辑,倍率为 1 时不应显示 Badge。

建议添加的测试
test("does not render cost multiplier badge when multiplier equals 1", () => {
  mockIsLoading = false;
  mockIsError = false;
  mockError = null;
  mockHasNextPage = false;
  mockIsFetchingNextPage = false;

  mockLogs = [makeLog({ id: 1, costMultiplier: "1" })];
  const html = renderToStaticMarkup(
    <VirtualizedLogsTable filters={{}} autoRefreshEnabled={false} />
  );
  expect(html).not.toContain("x1.00");
});
src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx (2)

373-377: 倍率显示格式不一致

在 Tooltip 中使用 {multiplier.toFixed(2)}x(后缀),而第 197 行的 Badge 中使用 ×{multiplier.toFixed(2)}(前缀)。同一文件内显示格式不一致可能造成用户困惑。

建议统一格式
-                              {hasCostBadge && multiplier != null ? (
-                                <div>
-                                  {t("logs.billingDetails.multiplier")}: {multiplier.toFixed(2)}x
-                                </div>
-                              ) : null}
+                              {hasCostBadge && multiplier != null ? (
+                                <div>
+                                  {t("logs.billingDetails.multiplier")}: ×{multiplier.toFixed(2)}
+                                </div>
+                              ) : null}

96-114: 可选:考虑提取倍率计算逻辑为共享工具函数

此处的倍率计算逻辑与 virtualized-logs-table.tsx 中的实现相似。如果未来需要在其他地方复用或修改该逻辑,可以考虑提取为共享的工具函数,例如:

// lib/utils/cost-multiplier.ts
export function getCostMultiplierInfo(providerChain, costMultiplier) {
  // ... shared logic
  return { multiplier, hasCostBadge };
}

这是可选的改进建议,当前实现已满足需求。

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

📥 Commits

Reviewing files that changed from the base of the PR and between 373220d and 179db42.

📒 Files selected for processing (8)
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx
  • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.test.tsx
  • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

No emoji characters in any code, comments, or string literals

Files:

  • src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx
  • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx
**/*.{ts,tsx,jsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,jsx,js}: All user-facing strings must use i18n (5 languages supported: zh-CN, en, ja, ko, de). Never hardcode display text
Use path alias @/ to map to ./src/
Use Biome for code formatting with configuration: double quotes, trailing commas, 2-space indent, 100 character line width
Prefer named exports over default exports
Use next-intl for internationalization
Use Next.js 16 App Router with Hono for API routes

Files:

  • src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx
  • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place unit tests in tests/unit/, integration tests in tests/integration/, and source-adjacent tests in src/**/*.test.ts

Files:

  • src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use React 19, shadcn/ui, Tailwind CSS, and Recharts for the UI layer

Files:

  • src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx
  • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx
🧠 Learnings (3)
📚 Learning: 2026-01-07T17:05:37.171Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-07T17:05:37.171Z
Learning: Applies to **/*.{tsx,jsx} : Use React 19, shadcn/ui, Tailwind CSS, and Recharts for the UI layer

Applied to files:

  • src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx
📚 Learning: 2026-01-10T06:20:13.376Z
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 573
File: src/app/[locale]/settings/prices/_components/sync-conflict-dialog.tsx:42-53
Timestamp: 2026-01-10T06:20:13.376Z
Learning: In the claude-code-hub project, model pricing display (in files like `src/app/[locale]/settings/prices/_components/sync-conflict-dialog.tsx`) intentionally uses hardcoded USD currency symbol (`$`) and per-million-token notation (`/M`, `/img`) because the system exclusively tracks LiteLLM pricing in USD and the notation is industry standard. Configurability was deemed unnecessary complexity.

Applied to files:

  • src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx
📚 Learning: 2026-01-10T06:20:32.687Z
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 573
File: src/app/[locale]/settings/prices/_components/sync-litellm-button.tsx:118-125
Timestamp: 2026-01-10T06:20:32.687Z
Learning: In `src/app/[locale]/settings/prices/_components/sync-litellm-button.tsx`, the "Cancel" button in the SyncConflictDialog is intentionally designed to call `onConfirm([])`, which triggers `doSync([])` to continue the sync while skipping (not overwriting) conflicting manual prices. This is the desired product behavior to allow users to proceed with LiteLLM sync for non-conflicting models while preserving their manual price entries.

Applied to files:

  • src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx
🧬 Code graph analysis (5)
src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx (2)
src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx (1)
  • ProviderChainPopover (38-131)
src/components/ui/badge.tsx (1)
  • Badge (39-39)
src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx (2)
src/repository/usage-logs.ts (1)
  • UsageLogRow (30-62)
src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx (1)
  • VirtualizedLogsTable (51-629)
src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx (2)
src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx (1)
  • ProviderChainPopover (38-131)
src/lib/utils/provider-chain-formatter.ts (1)
  • formatProviderSummary (145-194)
src/app/[locale]/dashboard/logs/_components/provider-chain-popover.test.tsx (1)
src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx (1)
  • ProviderChainPopover (38-131)
src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx (1)
src/repository/usage-logs.ts (1)
  • UsageLogRow (30-62)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Docker Build Test
  • GitHub Check: 🌐 API Tests
  • GitHub Check: pr-description
  • GitHub Check: pr-review
  • GitHub Check: check-codex-status
🔇 Additional comments (12)
src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx (1)

55-106: 布局收缩策略(w-full + min-w-0 + flex-1/shrink-0)合理,能稳定避免徽标/图标挤压文本

这组调整能确保名称区域可截断且不会把右侧元素(Badge/Info)挤出或撑破布局;同时 aria-label 仍保持可读。

src/app/[locale]/dashboard/logs/_components/provider-chain-popover.test.tsx (1)

1-149: 回归测试覆盖了关键 class(w-full/min-w-0/flex-1),方向正确;但文件落点/扩展名建议确认

用例能锁住这次修复的核心布局 contract。另同样需要确认 src/**/**.test.tsx 是否符合仓库测试放置约定。As per coding guidelines, tests placement/naming should follow the specified patterns.

src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx (1)

1-658: 移除该审查意见 - 测试文件位置和命名符合现有代码规范

该文件的位置 (src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx) 和命名遵循了项目既定的惯例。在代码库中,React 组件测试使用 .test.tsx 扩展名并放置在源代码相邻位置(src/ 内),而非组件测试使用 .test.ts。此模式在整个代码库中一致使用。

Likely an incorrect or invalid review comment.

src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx (1)

1-184: 文件位置和扩展名符合约定,无需修改

该文件位置 src/app/[locale]/dashboard/logs/_components/ 符合 source-adjacent 测试约定。虽然编码指南提及 .test.ts,但 vitest 配置明确支持 src/**/*.{test,spec}.tsx 模式,且仓库中 18 个 .test.tsx 文件都是组件测试,包括同目录的其他组件测试文件(如 error-details-dialog.test.tsxprovider-chain-popover.test.tsx 等)。.test.tsx 扩展名对于包含 JSX 语法的组件测试是适当的。

src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx (1)

298-334: 布局修复和倍率计算逻辑正确

这段修改正确解决了 Provider 单元格溢出问题:

  1. 外层容器添加 w-full overflow-hidden 确保内容不会溢出
  2. ProviderChainPopover 包裹在 flex-1 min-w-0 overflow-hidden 的 div 中,允许其收缩
  3. 倍率 Badge 使用 shrink-0 保持固定宽度

倍率计算逻辑使用 Number() 转换和 Number.isFinite() 检查,正确过滤了 NaN、Infinity 等无效值。

src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx (4)

1-6: 导入顺序和组织

测试文件结构良好,导入了必要的测试工具。但建议将 react-dom/serverreact-dom/client 的导入与 React 核心导入分组。


9-14: 测试状态变量初始化

模块级可变变量用于控制 mock 行为是常见做法,但建议在每个测试前重置这些变量以避免测试间状态泄漏。当前在每个测试开头都有重置,这是好的做法。


164-180: 无效倍率测试覆盖全面

测试正确覆盖了 nullundefined、空字符串、"NaN""Infinity" 等无效值,验证这些情况下不会渲染错误的 Badge。


196-240: 滚动测试实现正确

测试使用真实 DOM 渲染和 act 来验证滚动行为,正确模拟了 scrollTop 设置和 scroll 事件分发。对 scrollTo 的 mock 处理考虑到了 happy-dom 的限制,清理逻辑也很完整。

src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx (3)

96-114: 倍率计算逻辑提取到顶层

将倍率计算逻辑从内联 IIFE 提取到 logs.map 回调顶部是合理的重构,提高了可读性并允许在多处复用 hasCostBadgemultiplier 值。

逻辑正确处理了空字符串、null、undefined 以及非有限数值的情况。


148-160: Provider 单元格布局修复

ProviderChainPopover 包裹在 w-full 的 div 中,确保其正确占用可用宽度。与 virtualized-logs-table.tsx 的修改保持一致。


188-199: 倍率 Badge 渲染逻辑正确

Badge 根据 multiplier > 1 使用不同颜色(橙色表示更贵,绿色表示更便宜),符合直觉的用户体验设计。

注意:此处使用 ×(乘号),与 virtualized-logs-table.tsx 中的 x 不一致,建议统一。

Comment on lines +516 to +562
test("scrolls to model redirect section when scrollToRedirect is true", async () => {
vi.useFakeTimers();
const container = document.createElement("div");
document.body.appendChild(container);

const scrollIntoViewMock = vi.fn();
const originalScrollIntoView = Element.prototype.scrollIntoView;
Object.defineProperty(Element.prototype, "scrollIntoView", {
value: scrollIntoViewMock,
configurable: true,
});

const root = createRoot(container);
await act(async () => {
root.render(
<NextIntlClientProvider locale="en" messages={messages} timeZone="UTC">
<ErrorDetailsDialog
externalOpen
statusCode={200}
errorMessage={null}
providerChain={null}
sessionId={null}
scrollToRedirect
originalModel={"m1"}
currentModel={"m2"}
/>
</NextIntlClientProvider>
);
});

await act(async () => {
vi.advanceTimersByTime(150);
});

expect(scrollIntoViewMock).toHaveBeenCalled();

await act(async () => {
root.unmount();
});

Object.defineProperty(Element.prototype, "scrollIntoView", {
value: originalScrollIntoView,
configurable: true,
});
vi.useRealTimers();
container.remove();
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

scrollIntoView + fake timers 建议用 try/finally 包裹,避免失败时污染后续用例

这里修改了 Element.prototype.scrollIntoView 并启用了 vi.useFakeTimers();若中途断言失败,恢复逻辑不会执行,可能导致后续测试串扰。建议用 try/finally 保证 useRealTimers 与原型恢复必定执行。

🤖 Prompt for AI Agents
In @src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx
around lines 516 - 562, Wrap the test "scrolls to model redirect section when
scrollToRedirect is true" body in a try/finally so that
Element.prototype.scrollIntoView is always restored and vi.useRealTimers()
always called; capture the originalScrollIntoView before overriding, set
Element.prototype.scrollIntoView to scrollIntoViewMock for the test, run the
assertions, and in finally restore Element.prototype.scrollIntoView =
originalScrollIntoView and call vi.useRealTimers(), also ensure
container.remove() happens in the finally block to avoid leaking DOM between
tests.

Comment on lines +539 to +552
{(() => {
if (costMultiplier === "" || costMultiplier == null) return null;
const multiplier = Number(costMultiplier);
if (!Number.isFinite(multiplier) || multiplier === 1) return null;

return (
<div className="flex justify-between">
<span className="text-muted-foreground">
{t("logs.billingDetails.multiplier")}:
</span>
<span className="font-mono">{multiplier.toFixed(2)}x</span>
</div>
);
})()}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

空白字符串会被当作 0 渲染为 0.00x,建议一并视为“无倍率”

当前仅排除了 "" | null | undefined,但像 " "Number(" ") === 0isFinite,最终显示 0.00x,通常更像“无效输入”。建议先 trim() 再判断空字符串。

建议修改(仅限本段)
-                        {(() => {
-                          if (costMultiplier === "" || costMultiplier == null) return null;
-                          const multiplier = Number(costMultiplier);
+                        {(() => {
+                          if (costMultiplier == null) return null;
+                          const raw = costMultiplier.trim();
+                          if (raw === "") return null;
+                          const multiplier = Number(raw);
                           if (!Number.isFinite(multiplier) || multiplier === 1) return null;

                           return (
                             <div className="flex justify-between">
                               <span className="text-muted-foreground">
                                 {t("logs.billingDetails.multiplier")}:
                               </span>
                               <span className="font-mono">{multiplier.toFixed(2)}x</span>
                             </div>
                           );
                         })()}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{(() => {
if (costMultiplier === "" || costMultiplier == null) return null;
const multiplier = Number(costMultiplier);
if (!Number.isFinite(multiplier) || multiplier === 1) return null;
return (
<div className="flex justify-between">
<span className="text-muted-foreground">
{t("logs.billingDetails.multiplier")}:
</span>
<span className="font-mono">{multiplier.toFixed(2)}x</span>
</div>
);
})()}
{(() => {
if (costMultiplier == null) return null;
const raw = costMultiplier.trim();
if (raw === "") return null;
const multiplier = Number(raw);
if (!Number.isFinite(multiplier) || multiplier === 1) return null;
return (
<div className="flex justify-between">
<span className="text-muted-foreground">
{t("logs.billingDetails.multiplier")}:
</span>
<span className="font-mono">{multiplier.toFixed(2)}x</span>
</div>
);
})()}
🤖 Prompt for AI Agents
In @src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx around
lines 539 - 552, The rendering shows "0.00x" for inputs like "   " because
costMultiplier is not trimmed before numeric conversion; update the IIFE around
costMultiplier to first compute const trimmed = costMultiplier?.trim(); treat
trimmed === "" or trimmed == null as no multiplier, then parse const multiplier
= Number(trimmed) and keep the existing checks (Number.isFinite(multiplier) &&
multiplier !== 1) before rendering; reference costMultiplier and multiplier in
the change.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

No significant issues identified in this PR. Given the XL size and that changes span UI layout, multiplier validation, and multiple new tests, consider splitting future work into separate PRs (layout-only, multiplier-only, tests-only) to reduce review load.

PR Size: XL

  • Lines changed: 1244
  • Files changed: 8

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate
  • Code clarity - Good

Automated review by Codex AI

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Reviewed PR #581 (8 files, 1244 lines changed) across logic, security, error handling, types, comments, tests, and simplification; no issues met the reporting threshold on diff lines.
  • Applied label size/XL via gh pr edit.
  • Submitted the review summary via gh pr review --comment (no inline comments posted since no qualifying issues were found).

@github-actions github-actions bot removed the size/XL Extra Large PR (> 1000 lines) label Jan 10, 2026
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

This PR fixes layout overflow issues in the logs table provider column and improves validation for cost multiplier values to prevent rendering NaN/Infinity. The implementation is clean, well-tested, and follows established patterns in the codebase.

PR Size: XL

  • Lines changed: 1244 (1102 additions, 142 deletions)
  • Files changed: 8

Note for XL PRs: While this PR touches 8 files, the changes are tightly scoped to a single feature area (logs components) and the bulk of additions are test files. The implementation changes themselves are minimal and focused. No split is recommended.

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Comprehensive (new tests for all edge cases)
  • Code clarity - Good

Analysis Details

Layout Fixes (provider-chain-popover.tsx, virtualized-logs-table.tsx):

  • Added w-full, min-w-0, flex-1 classes to enable proper flex shrinking
  • Wrapped ProviderChainPopover in a shrinkable container to prevent badge overflow
  • Changes follow established CSS flexbox patterns in the codebase

Multiplier Validation (all 3 table components):

  • Replaced parseFloat(String(...)) with Number() for cleaner type conversion
  • Added explicit checks for empty string, null, and Number.isFinite() to filter NaN/Infinity
  • Consistent validation logic across all components

Test Coverage:

  • New test files for provider-chain-popover, virtualized-logs-table, and usage-logs-table
  • Tests cover all edge cases: null, undefined, empty string, "NaN", "Infinity", and valid multipliers
  • Additional coverage for layout classes, blocked states, and pagination callbacks

Automated review by Claude AI

@ding113 ding113 merged commit 9901cff into ding113:dev Jan 10, 2026
13 of 14 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Jan 10, 2026
@github-actions github-actions bot mentioned this pull request Jan 10, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:UI bug Something isn't working

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants