Skip to content

feat(leaderboard): add user tag and group filters for user ranking#607

Merged
ding113 merged 2 commits intodevfrom
feat/leaderboard-user-filter-606
Jan 13, 2026
Merged

feat(leaderboard): add user tag and group filters for user ranking#607
ding113 merged 2 commits intodevfrom
feat/leaderboard-user-filter-606

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Jan 13, 2026

Summary

  • Add filtering capability to leaderboard user ranking by user tags and groups
  • Filters use OR logic (match any tag or any group)
  • Admin-only feature, only visible when scope=user

Changes

Repository Layer (src/repository/leaderboard.ts)

  • Add UserLeaderboardFilters interface with userTags?: string[] and userGroups?: string[]
  • Implement SQL filtering using JSONB containment for tags and regex split for groups

Cache Layer (src/lib/redis/leaderboard-cache.ts)

  • Extend LeaderboardFilters to include userTags and userGroups
  • Update buildCacheKey() to include sorted user filters in cache key
  • Update queryDatabase() to pass filters when scope=user

API Route (src/app/api/leaderboard/route.ts)

  • Parse userTags and userGroups query params (CSV format)
  • Validate: trim, split by comma, filter empty, limit to 20 items max

Frontend (src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx)

  • Add state for userTagFilters and userGroupFilters
  • Add two TagInput components (admin-only, only when scope=user)
  • Append filters to fetch URL

i18n

  • Add leaderboard.filters.userTagsPlaceholder and userGroupsPlaceholder for 5 languages

Verification

  • TypeScript type check passed
  • Biome lint check passed
  • Production build succeeded
  • 134 test files, 870 tests passed

Closes #606

Greptile Overview

Greptile Summary

Adds comprehensive filtering capability to the user leaderboard by user tags and groups, enabling admins to drill down into specific user segments.

Key Changes:

  • Repository Layer: Implements SQL filtering using JSONB containment (@>) for tags and regexp_split_to_array for comma-separated groups
  • Cache Layer: Extends cache keys to include sorted filter values, ensuring proper cache isolation per filter combination
  • API Route: Parses and validates CSV-formatted query parameters with proper trimming and a 20-item limit
  • Frontend: Adds admin-only TagInput components that appear only when scope=user, with proper state management and URL parameter encoding
  • i18n: Provides translations for filter placeholders across 5 languages

Implementation Quality:

  • Proper use of parameterized SQL queries prevents injection attacks
  • Cache key includes sorted filters to ensure consistent cache hits
  • OR logic allows matching users with any specified tag or group
  • Filter state properly triggers data refetch via useEffect dependencies
  • Admin-only UI restriction aligns with feature scope

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk - well-structured implementation with proper security measures
  • Score reflects clean implementation with parameterized SQL queries preventing injection, proper cache invalidation, comprehensive i18n support, admin-only UI restrictions, thorough testing (870 tests passed), and validated production build
  • No files require special attention - all implementations follow established patterns and security best practices

Important Files Changed

File Analysis

Filename Score Overview
src/repository/leaderboard.ts 5/5 Added UserLeaderboardFilters interface and filtering logic using JSONB containment for tags and regex split for groups with OR logic between filters
src/lib/redis/leaderboard-cache.ts 5/5 Extended cache key builder to include sorted user filters, updated queryDatabase to pass filters when scope=user
src/app/api/leaderboard/route.ts 5/5 Added parsing and validation of userTags and userGroups query params (CSV format, trimmed, max 20 items), passes filters to cache layer
src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx 5/5 Added state for user tag/group filters, rendered two TagInput components (admin-only when scope=user), appends filters to fetch URL

Sequence Diagram

sequenceDiagram
    participant User as Admin User
    participant UI as LeaderboardView
    participant API as /api/leaderboard
    participant Cache as Redis Cache
    participant Repo as Repository Layer
    participant DB as PostgreSQL

    User->>UI: Select user tags/groups filters
    UI->>UI: Update state (userTagFilters, userGroupFilters)
    UI->>API: GET /api/leaderboard?scope=user&userTags=tag1,tag2&userGroups=group1
    API->>API: Parse & validate filters (trim, max 20)
    API->>Cache: getLeaderboardWithCache(period, scope, filters)
    Cache->>Cache: buildCacheKey(scope, filters) with sorted tags/groups
    Cache->>Cache: Check Redis for cached data
    alt Cache Hit
        Cache-->>API: Return cached data
    else Cache Miss
        Cache->>Repo: queryDatabase(period, scope, filters)
        Repo->>Repo: Build WHERE conditions
        Note over Repo: Tags: JSONB @> operator (OR logic)<br/>Groups: regexp_split_to_array (OR logic)<br/>Combined: (tags OR groups)
        Repo->>DB: SELECT with INNER JOIN users<br/>WHERE filters applied
        DB-->>Repo: Aggregated user rankings
        Repo-->>Cache: Return rankings
        Cache->>Cache: Store in Redis (60s TTL)
        Cache-->>API: Return rankings
    end
    API->>API: Format currency fields
    API-->>UI: Return filtered leaderboard data
    UI->>UI: Render filtered results
Loading

)

Add filtering capability to the leaderboard user ranking by:
- userTags: filter users by their tags (OR logic)
- userGroups: filter users by their providerGroup (OR logic)

Changes:
- Repository: Add UserLeaderboardFilters interface and SQL filtering
- Cache: Extend LeaderboardFilters and include filters in cache key
- API: Parse userTags/userGroups query params (CSV format, max 20)
- Frontend: Add TagInput filters (admin-only, user scope only)
- i18n: Add translation keys for 5 languages

Closes #606
@coderabbitai
Copy link

coderabbitai bot commented Jan 13, 2026

📝 Walkthrough

Walkthrough

在排行榜功能中增加了按用户标签与用户组的筛选:更新了多语言本地化占位符、前端 UI(TagInput 与状态)、API 路由解析新查询参数、Redis 缓存键与查询分支以包含用户过滤,以及仓储层 SQL 查询以按标签/分组过滤用户排行数据。

Changes

Cohort / File(s) 变更摘要
本地化资源
messages/en/dashboard.json, messages/ja/dashboard.json, messages/ru/dashboard.json, messages/zh-CN/dashboard.json, messages/zh-TW/dashboard.json
leaderboard 下新增 filters 对象,包含 userTagsPlaceholderuserGroupsPlaceholder 占位文本(每文件 +4/-0)。
前端 - 排行界面
src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx
添加 TagInput 依赖、引入 userTagFiltersuserGroupFilters 状态;在 user scope 时将过滤器作为查询参数拼接到数据请求;添加条件渲染的过滤输入控件并将其纳入 effect 依赖(+37/-1)。
后端路由
src/app/api/leaderboard/route.ts
解析 userTagsuserGroups 查询参数(限 scope=user),新增 parseListParam,将解析结果传入缓存层调用(由单一参数改为 options 对象),并在响应/日志中记录这些过滤值(+22/-1)。
缓存层(Redis)
src/lib/redis/leaderboard-cache.ts
扩展 LeaderboardFilters 接口加入 userTags/userGroups,新增 UserLeaderboardFilters 类型;缓存键构造包含用户过滤段(:tags:..., :groups:...);在 user 范围的查询路径中传递 userFilters 以影响缓存与回源逻辑(+30/-11)。
仓储层(数据库查询)
src/repository/leaderboard.ts
新增并导出 UserLeaderboardFiltersuserTags?: string[], userGroups?: string[]);将各周期与自定义区间的查询签名扩展为可选 userFilters 参数;在 findLeaderboardWithTimezone 中以 OR 逻辑将标签/分组条件加入动态 WHERE 构造;将所有调用点贯穿传递 userFilters(+63/-18)。

Estimated code review effort

🎯 3 (中等) | ⏱️ ~40 分钟

功能概览

该变更为排行榜添加了基于用户标签和用户组的筛选功能。涵盖五种语言的本地化字符串、前端UI组件增强、API参数解析、缓存层更新及数据库查询逻辑扩展,实现完整的端到端筛选功能链路。

变更内容

功能模块 / 文件 变更摘要
本地化资源
messages/en/dashboard.json, messages/ja/dashboard.json, messages/ru/dashboard.json, messages/zh-CN/dashboard.json, messages/zh-TW/dashboard.json
在leaderboard对象下新增filters字段,包含userTagsPlaceholder和userGroupsPlaceholder两个占位符,为五种语言提供一致的UI文本 (+4/-0 每文件)
前端组件
src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx
新增TagInput依赖、userTagFilters和userGroupFilters状态变量;扩展用户scope的API URL构造以包含对应查询参数;添加条件渲染的UI控制组件(仅在admin且user scope时可见);扩展effect依赖数组 (+37/-1)
API路由层
src/app/api/leaderboard/route.ts
新增userTags和userGroups查询参数解析(仅限user scope);转换为数组并传递给getLeaderboardWithCache;更新函数签名为options对象模式;扩展日志记录context (+24/-1)
缓存层
src/lib/redis/leaderboard-cache.ts
扩展LeaderboardFilters接口新增userTags和userGroups字段;引入UserLeaderboardFilters类型;缓存键构造逻辑扩展以追加用户过滤器段(:tags:..., :groups:...);调整user scope查询流程传递userFilters参数 (+30/-11)
仓储层
src/repository/leaderboard.ts
新增UserLeaderboardFilters接口;更新findDailyLeaderboard、findWeeklyLeaderboard、findMonthlyLeaderboard、findAllTimeLeaderboard、findCustomRangeLeaderboard函数签名添加可选userFilters参数;增强findLeaderboardWithTimezone以构建OR逻辑的标签和分组WHERE条件;使用whereConditions数组进行动态条件组合 (+65/-18)

代码审查工作量评估

🎯 3 (中等) | ⏱️ ~40 分钟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed 标题清晰明确地概括了主要变更:为用户排行添加按标签和分组进行筛选的功能,与changeset的核心内容完全一致。
Description check ✅ Passed 描述详细说明了所有主要变更,包括仓储层、缓存层、API路由、前端和国际化等各个方面的改动,与changeset紧密相关。
Linked Issues check ✅ Passed 代码变更完全满足#606的需求:实现了按用户标签或分组筛选的功能,支持多选,采用OR逻辑,且仅在scope=user时对管理员可见。
Out of Scope Changes check ✅ Passed 所有代码变更均与#606中定义的目标一致,没有发现超出范围的改动。所有修改都专注于实现用户标签和分组筛选功能。
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%.

✏️ 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
  • Commit unit tests in branch feat/leaderboard-user-filter-606

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

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ding113, 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!

This pull request significantly enhances the leaderboard functionality by introducing advanced filtering options for user rankings. Administrators can now precisely narrow down user data based on custom tags and groups, providing a more powerful tool for analysis and management. This change improves the utility and flexibility of the leaderboard without affecting general user experience, as the feature is restricted to admin users.

Highlights

  • Leaderboard Filtering: Introduced new filtering capabilities for the user leaderboard, allowing administrators to filter user rankings by specific user tags and user groups. These filters operate with OR logic, matching any specified tag or group.
  • Admin-Only Feature: The new user tag and group filters are an admin-only feature and are only visible and applicable when the leaderboard scope is set to 'user'.
  • Backend Implementation: The repository layer now supports filtering by user tags using PostgreSQL's JSONB containment and by user groups using regex splitting and the ANY operator. The cache layer has been updated to include these new filters in the cache key to ensure proper caching of filtered results.
  • API and Frontend Integration: The API route now parses and validates userTags and userGroups query parameters, limiting them to 20 items. The frontend integrates TagInput components for administrators to easily apply these filters, dynamically updating the leaderboard data.
  • Internationalization: Added new i18n keys for user tag and group filter placeholders across five supported languages.
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.

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

This pull request introduces filtering capabilities for the user leaderboard by tags and groups, a valuable enhancement for administrators. The changes span the full stack from the database repository to the frontend UI, including caching and API layers. The implementation is well-structured. My review focuses on improving database query performance for the new filters and enhancing code maintainability by refactoring duplicated logic in the API route and frontend components.

Comment on lines +384 to +405
<div className="flex flex-wrap gap-4 mb-4">
<div className="flex-1 min-w-[200px] max-w-[300px]">
<TagInput
value={userTagFilters}
onChange={setUserTagFilters}
placeholder={t("filters.userTagsPlaceholder")}
disabled={loading}
maxTags={20}
clearable
/>
</div>
<div className="flex-1 min-w-[200px] max-w-[300px]">
<TagInput
value={userGroupFilters}
onChange={setUserGroupFilters}
placeholder={t("filters.userGroupsPlaceholder")}
disabled={loading}
maxTags={20}
clearable
/>
</div>
</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

The two TagInput components for user tags and user groups are nearly identical. To reduce duplication and improve maintainability, you can define the filter configurations in an array and render the components in a loop. This makes the code cleaner and easier to extend with more filters in the future.

        <div className="flex flex-wrap gap-4 mb-4">
          {[
            {
              value: userTagFilters,
              onChange: setUserTagFilters,
              placeholder: t("filters.userTagsPlaceholder"),
            },
            {
              value: userGroupFilters,
              onChange: setUserGroupFilters,
              placeholder: t("filters.userGroupsPlaceholder"),
            },
          ].map((config, index) => (
            <div key={index} className="flex-1 min-w-[200px] max-w-[300px]">
              <TagInput
                value={config.value}
                onChange={config.onChange}
                placeholder={config.placeholder}
                disabled={loading}
                maxTags={20}
                clearable
              />
            </div>
          ))}
        </div>

Comment on lines 130 to 147
let userTags: string[] | undefined;
let userGroups: string[] | undefined;
if (scope === "user") {
if (userTagsParam) {
userTags = userTagsParam
.split(",")
.map((t) => t.trim())
.filter((t) => t.length > 0)
.slice(0, 20);
}
if (userGroupsParam) {
userGroups = userGroupsParam
.split(",")
.map((g) => g.trim())
.filter((g) => g.length > 0)
.slice(0, 20);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The logic for parsing userTagsParam and userGroupsParam is duplicated. To improve maintainability and reduce code duplication, this logic can be extracted into a reusable helper function.

    const parseListParams = (param: string | null): string[] | undefined => {
      if (param === null) {
        return undefined;
      }
      return param
        .split(",")
        .map((item) => item.trim())
        .filter((item) => item.length > 0)
        .slice(0, 20);
    };

    let userTags: string[] | undefined;
    let userGroups: string[] | undefined;
    if (scope === "user") {
      userTags = parseListParams(userTagsParam);
      userGroups = parseListParams(userGroupsParam);
    }

Comment on lines 184 to 186
const tagConditions = normalizedTags.map(
(tag) => sql`${users.tags} @> ${JSON.stringify([tag])}::jsonb`
);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

For checking the existence of a top-level element in a JSONB array, using the ? operator is more direct and generally more performant than the @> containment operator. This change simplifies the query and better expresses the intent of checking for a tag's presence in the array.

To ensure this query is efficient, a GIN index should be present on the users.tags column.

Suggested change
const tagConditions = normalizedTags.map(
(tag) => sql`${users.tags} @> ${JSON.stringify([tag])}::jsonb`
);
const tagConditions = normalizedTags.map(
(tag) => sql`${users.tags} ? ${tag}`
);

Comment on lines +193 to +196
const groupConditions = normalizedGroups.map(
(group) =>
sql`${group} = ANY(regexp_split_to_array(coalesce(${users.providerGroup}, ''), '\\s*,\\s*'))`
);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The use of regexp_split_to_array on the users.providerGroup column for every row can cause significant performance issues on a large users table. This operation is not SARGable, meaning it prevents the database from using an index efficiently and forces a full table scan with a CPU-intensive operation per row.

While functionally correct for the current schema, this approach may not scale well. For a long-term solution, consider normalizing the schema by introducing a dedicated user_groups table and a many-to-many join table between users and groups. This would allow for much more performant indexed lookups.

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.

No files 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: 1

🤖 Fix all issues with AI agents
In @messages/zh-CN/dashboard.json:
- Around line 325-329: The translation for the key "userGroupsPlaceholder" may
be inconsistent—confirm whether the standard term for "group" in the project is
"分组" or "组/用户群组" and update the value of "userGroupsPlaceholder" in
messages/zh-CN/dashboard.json accordingly; locate the object "filters" and
change "userGroupsPlaceholder": "按用户分组筛选..." to the agreed canonical translation
(e.g., "按用户组筛选..." or "按用户群组筛选...") so it matches other occurrences of
group-related keys across the codebase and avoids confusion with other
domain-specific "分组" usages like supplier grouping.
🧹 Nitpick comments (1)
src/lib/redis/leaderboard-cache.ts (1)

299-316: 缓存失效函数未支持用户筛选参数

invalidateLeaderboardCache 函数没有接受 filters 参数,这意味着当使用用户筛选时,无法精确失效特定筛选条件的缓存。

目前可能不是问题(因为缓存 TTL 只有 60 秒),但如果将来需要主动失效带筛选条件的缓存,需要更新此函数。

可选的改进(如需要精确失效带筛选条件的缓存)
 export async function invalidateLeaderboardCache(
   period: LeaderboardPeriod,
   currencyDisplay: string,
-  scope: LeaderboardScope = "user"
+  scope: LeaderboardScope = "user",
+  dateRange?: DateRangeParams,
+  filters?: LeaderboardFilters
 ): Promise<void> {
   const redis = getRedisClient();
   if (!redis) {
     return;
   }

-  const cacheKey = buildCacheKey(period, currencyDisplay, scope);
+  const cacheKey = buildCacheKey(period, currencyDisplay, scope, dateRange, filters);
📜 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 4dad830 and 92ad138.

📒 Files selected for processing (9)
  • messages/en/dashboard.json
  • messages/ja/dashboard.json
  • messages/ru/dashboard.json
  • messages/zh-CN/dashboard.json
  • messages/zh-TW/dashboard.json
  • src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx
  • src/app/api/leaderboard/route.ts
  • src/lib/redis/leaderboard-cache.ts
  • src/repository/leaderboard.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Never use emoji characters in any code, comments, or string literals

Files:

  • src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx
  • src/repository/leaderboard.ts
  • src/app/api/leaderboard/route.ts
  • src/lib/redis/leaderboard-cache.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: All user-facing strings must use i18n (5 languages supported: zh-CN, zh-TW, en, ja, ru). Never hardcode display text
Use path alias @/ to reference files in ./src/ directory
Format code with Biome using: double quotes, trailing commas, 2-space indent, 100 character line width

Files:

  • src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx
  • src/repository/leaderboard.ts
  • src/app/api/leaderboard/route.ts
  • src/lib/redis/leaderboard-cache.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer named exports over default exports

Files:

  • src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx
  • src/repository/leaderboard.ts
  • src/app/api/leaderboard/route.ts
  • src/lib/redis/leaderboard-cache.ts
src/repository/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Drizzle ORM for data access in the repository layer

Files:

  • src/repository/leaderboard.ts
src/app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

API routes should use Hono framework and follow Next.js 16 App Router conventions

Files:

  • src/app/api/leaderboard/route.ts
🧠 Learnings (2)
📚 Learning: 2026-01-05T03:02:14.502Z
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 539
File: src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx:66-66
Timestamp: 2026-01-05T03:02:14.502Z
Learning: In the claude-code-hub project, the translations.actions.addKey field in UserKeyTableRowProps is defined as optional for backward compatibility, but all actual callers in the codebase provide the complete translations object. The field has been added to all 5 locale files (messages/{locale}/dashboard.json).

Applied to files:

  • messages/zh-CN/dashboard.json
📚 Learning: 2026-01-05T03:01:39.354Z
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 539
File: src/types/user.ts:158-170
Timestamp: 2026-01-05T03:01:39.354Z
Learning: In TypeScript interfaces, explicitly document and enforce distinct meanings for null and undefined. Example: for numeric limits like limitTotalUsd, use 'number | null | undefined' when null signifies explicitly unlimited (e.g., matches DB schema or special UI logic) and undefined signifies 'inherit default'. This pattern should be consistently reflected in type definitions across related fields to preserve semantic clarity between database constraints and UI behavior.

Applied to files:

  • src/repository/leaderboard.ts
  • src/app/api/leaderboard/route.ts
  • src/lib/redis/leaderboard-cache.ts
🧬 Code graph analysis (3)
src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx (1)
src/components/ui/tag-input.tsx (1)
  • TagInput (45-430)
src/app/api/leaderboard/route.ts (1)
src/lib/redis/leaderboard-cache.ts (1)
  • getLeaderboardWithCache (204-291)
src/lib/redis/leaderboard-cache.ts (1)
src/repository/leaderboard.ts (6)
  • UserLeaderboardFilters (25-30)
  • findCustomRangeLeaderboard (242-248)
  • findDailyLeaderboard (75-80)
  • findWeeklyLeaderboard (97-102)
  • findMonthlyLeaderboard (86-91)
  • findAllTimeLeaderboard (107-112)
⏰ 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). (6)
  • GitHub Check: pr-review
  • GitHub Check: pr-label
  • GitHub Check: pr-description
  • GitHub Check: 🌐 API Tests
  • GitHub Check: Docker Build Test
  • GitHub Check: dev-build-deploy
🔇 Additional comments (20)
messages/en/dashboard.json (1)

325-329: 新增 i18n 键路径与 PR 目标一致,LGTM。
leaderboard.filters.* 放在 leaderboard 下(而非 leaderboard.permission 下)更契合“filters”语义,也更便于 UI 侧按 scope 展示。

messages/ja/dashboard.json (1)

324-328: 结构对齐其它 locale,LGTM。
建议仅确认一下产品侧是否更偏好「フィルタ」还是「絞り込み」用词(非阻塞)。

messages/ru/dashboard.json (1)

324-328: 新增占位符键与其它语言版本保持一致,LGTM。

messages/zh-TW/dashboard.json (1)

325-329: 与其它 locale 对齐,文案清晰,LGTM。

src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx (4)

10-10: LGTM!

新增的 TagInput 导入和 userTagFilters/userGroupFilters 状态变量定义正确,遵循了文件中的现有模式。

Also applies to: 55-56


102-109: LGTM!

URL 构建逻辑正确:

  • 仅当 scope === "user" 时才添加用户筛选参数
  • 使用 encodeURIComponent 正确编码逗号分隔的值
  • 空数组时不添加参数,避免不必要的 URL 参数

134-134: LGTM!

Effect 依赖项正确更新,包含了新的 userTagFiltersuserGroupFilters 状态,确保筛选条件变化时触发数据重新加载。


383-406: LGTM!

TagInput 组件配置合理:

  • 仅对管理员 (isAdmin) 且 scope === "user" 时显示
  • maxTags={20} 与后端限制保持一致
  • 使用 i18n 获取 placeholder 文本
  • disabled={loading} 在加载时禁用输入
  • clearable 属性允许用户清空筛选条件
src/app/api/leaderboard/route.ts (3)

78-79: 考虑是否需要在 API 层强制执行管理员权限检查

根据 PR 描述,用户标签和用户分组筛选是"admin-only feature"。当前实现仅在前端隐藏了这些筛选控件,但 API 层允许任何有权限访问排行榜的用户使用这些筛选参数。

如果这是预期行为(即筛选参数本身不泄露敏感信息,只是缩小结果范围),当前实现是可以接受的。但如果希望严格限制为管理员专用,建议添加权限检查:

可选的权限检查实现
     let userTags: string[] | undefined;
     let userGroups: string[] | undefined;
     if (scope === "user") {
+      // 用户筛选参数仅限管理员使用
+      if ((userTagsParam || userGroupsParam) && !isAdmin) {
+        return NextResponse.json(
+          { error: "用户标签和分组筛选仅限管理员使用" },
+          { status: 403 }
+        );
+      }
       if (userTagsParam) {

请确认是否需要在 API 层限制这些筛选参数仅限管理员使用。

Also applies to: 130-147


155-155: LGTM!

筛选参数正确传递给 getLeaderboardWithCache,与 src/lib/redis/leaderboard-cache.ts 中的 LeaderboardFilters 接口定义一致。


186-187: LGTM!

日志记录正确包含了新的筛选参数,便于调试和审计。

src/lib/redis/leaderboard-cache.ts (4)

47-48: LGTM!

LeaderboardFilters 接口正确扩展,包含了 userTagsuserGroups 可选字段,与 API 路由层传入的参数结构保持一致。


65-74: LGTM!

缓存键构建逻辑正确:

  • 仅当 scope === "user" 时才添加用户筛选后缀
  • 使用 [...filters.userTags].sort() 创建副本后排序,避免修改原数组
  • 排序确保相同筛选条件(不同输入顺序)生成相同的缓存键

106-109: LGTM!

userFilters 对象仅在存在有效筛选条件时构建,避免不必要的对象创建和传递。


125-137: LGTM!

userFilters 正确传递给所有用户范围的排行榜查询函数(daily/weekly/monthly/allTime),与 src/repository/leaderboard.ts 中更新的函数签名一致。

src/repository/leaderboard.ts (5)

22-30: LGTM!

UserLeaderboardFilters 接口定义清晰,JSDoc 注释明确说明了 OR 逻辑的语义。基于 learnings,此接口正确使用了 undefined 表示"不筛选"的语义。


190-198: 分组筛选逻辑正确但可能存在性能考量

使用 regexp_split_to_arrayproviderGroup 字段进行运行时拆分是正确的实现方式,能够处理逗号分隔的多分组值。

需要注意:

  • 该操作会对每行执行正则表达式拆分,在大数据量下可能影响查询性能
  • 如果 providerGroup 字段的使用频率较高,未来可考虑将其规范化为独立的关联表

当前实现对于预期的数据规模应该是可接受的。


200-206: LGTM!

筛选条件的组合逻辑正确实现了 PR 需求中的 OR 语义:

  • 当同时提供标签和分组时,用 OR 连接两个条件
  • 最终与其他 WHERE 条件(deletedAt、日期等)用 AND 连接

这意味着:匹配任一指定标签 匹配任一指定分组的用户都会被包含在结果中。

Also applies to: 226-226


75-79: LGTM!

所有公开的排行榜查询函数签名已正确更新,接受可选的 userFilters 参数并传递给内部的 findLeaderboardWithTimezone 函数。保持了向后兼容性。

Also applies to: 86-90, 97-101, 107-111, 242-247


175-206: 当前实现已遵循 Drizzle ORM 的参数化安全机制

Drizzle ORM 0.44 版本的 sql 模板标签已内置参数化处理,能够防止 SQL 注入。Web 搜索确认该版本无已知的 SQL 注入漏洞。

具体分析:

  • 时区参数(Line 176):来自 getEnvConfig().TZ 的环境配置,非用户输入
  • 标签筛选(Line 185):JSON.stringify([tag]) 为标准做法,用户输入经 .trim().filter(Boolean) 规范化,通过参数化查询保护
  • 分组筛选(Line 195):用户输入同样经规范化处理,sql 模板会自动参数化

代码在当前形式下是安全的,无需修改。

Comment on lines +325 to 329
},
"filters": {
"userTagsPlaceholder": "按用户标签筛选...",
"userGroupsPlaceholder": "按用户分组筛选..."
}
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

建议确认「userGroups」中文术语是否应为「用户组/用户群组」而非「用户分组」。
如果系统内其它地方把 group 统一翻译成「分组」,那现在的文案也没问题;主要是避免与「供应商分组」等概念混淆。

🤖 Prompt for AI Agents
In @messages/zh-CN/dashboard.json around lines 325 - 329, The translation for
the key "userGroupsPlaceholder" may be inconsistent—confirm whether the standard
term for "group" in the project is "分组" or "组/用户群组" and update the value of
"userGroupsPlaceholder" in messages/zh-CN/dashboard.json accordingly; locate the
object "filters" and change "userGroupsPlaceholder": "按用户分组筛选..." to the agreed
canonical translation (e.g., "按用户组筛选..." or "按用户群组筛选...") so it matches other
occurrences of group-related keys across the codebase and avoids confusion with
other domain-specific "分组" usages like supplier grouping.

@github-actions github-actions bot added the size/S Small PR (< 200 lines) label Jan 13, 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 adds user tag and group filtering capability to the leaderboard user ranking feature. The implementation is clean, follows existing patterns in the codebase, and properly handles the new filter parameters across all layers (API, cache, repository).

PR Size: S

  • Lines changed: 207 (176 additions, 31 deletions)
  • Files changed: 9

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
    • Filter logic correctly uses OR semantics for tags and groups
    • Cache key generation properly sorts filters for consistency
    • SQL queries use proper JSONB containment operators for tags
  • Security (OWASP Top 10) - Clean
    • User input is properly sanitized (trim, filter empty, slice to limit)
    • SQL parameters are properly escaped via Drizzle ORM
    • Admin-only UI restriction is properly enforced
  • Error handling - Clean
    • Existing error handling patterns are maintained
    • API route has proper try/catch with logging
  • Type safety - Clean
    • New UserLeaderboardFilters interface is well-defined
    • Type flow is consistent across all layers
  • Documentation accuracy - Clean
    • JSDoc comments accurately describe OR logic for filters
  • Test coverage - Adequate
    • PR body indicates 870 tests passed
    • Note: No dedicated unit tests for the new filter logic were added, but this is consistent with existing leaderboard code which also lacks unit tests
  • Code clarity - Good
    • Implementation follows existing patterns in the codebase
    • i18n strings added for all 5 supported languages

Notes

  • The filter feature is admin-only and properly gated in the UI
  • Cache invalidation function doesn't include filter parameters, but this is acceptable since filtered queries will have different cache keys and will naturally expire

Automated review by Claude AI

- Use JSONB ? operator instead of @> for better performance
- Extract parseListParam helper to reduce code duplication
@ding113 ding113 merged commit 84c8ce6 into dev Jan 13, 2026
6 of 8 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Jan 13, 2026
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: 0

🧹 Nitpick comments (1)
src/repository/leaderboard.ts (1)

181-204: 筛选逻辑实现正确,OR 语义符合需求。

JSONB ? 操作符和 regexp_split_to_array 的使用是正确的。不过有一点性能上的考量:

providerGroup 字段当前没有索引,且 regexp_split_to_array 在每一行上执行正则分割,当数据量较大时可能影响查询性能。如果 providerGroup 字段经常用于筛选,可以考虑:

  1. 将其改为 JSONB 数组类型(与 tags 字段一致),以便利用 GIN 索引
  2. 或者在应用层预先获取匹配的用户 ID 列表

当前实现在中小规模数据集上应该没有问题,建议后续监控此查询的执行时间。

📜 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 92ad138 and 82ecc5d.

📒 Files selected for processing (2)
  • src/app/api/leaderboard/route.ts
  • src/repository/leaderboard.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Never use emoji characters in any code, comments, or string literals

Files:

  • src/repository/leaderboard.ts
  • src/app/api/leaderboard/route.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: All user-facing strings must use i18n (5 languages supported: zh-CN, zh-TW, en, ja, ru). Never hardcode display text
Use path alias @/ to reference files in ./src/ directory
Format code with Biome using: double quotes, trailing commas, 2-space indent, 100 character line width

Files:

  • src/repository/leaderboard.ts
  • src/app/api/leaderboard/route.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer named exports over default exports

Files:

  • src/repository/leaderboard.ts
  • src/app/api/leaderboard/route.ts
src/repository/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Drizzle ORM for data access in the repository layer

Files:

  • src/repository/leaderboard.ts
src/app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

API routes should use Hono framework and follow Next.js 16 App Router conventions

Files:

  • src/app/api/leaderboard/route.ts
🧠 Learnings (1)
📚 Learning: 2026-01-05T03:01:39.354Z
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 539
File: src/types/user.ts:158-170
Timestamp: 2026-01-05T03:01:39.354Z
Learning: In TypeScript interfaces, explicitly document and enforce distinct meanings for null and undefined. Example: for numeric limits like limitTotalUsd, use 'number | null | undefined' when null signifies explicitly unlimited (e.g., matches DB schema or special UI logic) and undefined signifies 'inherit default'. This pattern should be consistently reflected in type definitions across related fields to preserve semantic clarity between database constraints and UI behavior.

Applied to files:

  • src/repository/leaderboard.ts
  • src/app/api/leaderboard/route.ts
🧬 Code graph analysis (2)
src/repository/leaderboard.ts (2)
src/drizzle/schema.ts (2)
  • messageRequest (276-362)
  • users (35-86)
src/repository/_shared/message-request-conditions.ts (1)
  • EXCLUDE_WARMUP_CONDITION (9-9)
src/app/api/leaderboard/route.ts (1)
src/lib/redis/leaderboard-cache.ts (1)
  • getLeaderboardWithCache (204-291)
⏰ 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). (3)
  • GitHub Check: Docker Build Test
  • GitHub Check: 🌐 API Tests
  • GitHub Check: dev-build-deploy
🔇 Additional comments (8)
src/repository/leaderboard.ts (3)

22-30: 接口定义清晰,文档完善。

接口 UserLeaderboardFilters 的定义准确反映了 PR 需求,注释明确说明了 OR 逻辑语义。可选字段使用 undefined 表示"不筛选",符合 TypeScript 惯例。


75-112: 函数签名更新一致,保持向后兼容。

所有用户排行榜查询函数的签名更新统一,userFilters 作为可选参数不会破坏现有调用。


240-246: LGTM!

自定义日期范围查询函数的签名更新与其他周期函数保持一致。

src/app/api/leaderboard/route.ts (5)

78-79: 参数提取正确。

新增的查询参数提取逻辑清晰。


130-138: parseListParam 辅助函数实现简洁有效。

正确处理了空值、空白字符串和最大 20 项的限制。返回 undefined 而非空数组的语义更清晰。


140-145: 确认:用户筛选参数是否应在 API 层限制为仅管理员可用?

根据 PR 描述,用户标签和用户组筛选器是"admin-only"功能。当前代码仅在 scope="user" 时解析这些参数,但未检查用户是否为管理员。

如果设计意图是:

  • 仅前端隐藏(非管理员看不到 UI):当前实现没问题
  • API 层也要限制:需要添加权限检查

建议明确意图。如果需要在 API 层限制,可添加如下检查:

🔧 可选:添加管理员权限检查
     let userTags: string[] | undefined;
     let userGroups: string[] | undefined;
     if (scope === "user") {
-      userTags = parseListParam(userTagsParam);
-      userGroups = parseListParam(userGroupsParam);
+      // 用户筛选仅管理员可用
+      if (isAdmin) {
+        userTags = parseListParam(userTagsParam);
+        userGroups = parseListParam(userGroupsParam);
+      }
     }

147-154: 与缓存层的集成正确。

筛选参数以对象形式传递给 getLeaderboardWithCache,符合 LeaderboardFilters 接口定义。


176-187: 日志记录完整,便于调试和审计。

新增的筛选参数已添加到日志上下文中,与现有日志模式保持一致。

@github-actions github-actions bot mentioned this pull request Jan 19, 2026
9 tasks
ding113 added a commit that referenced this pull request Jan 20, 2026
* fix: 清理 usage-doc / big-screen i18n 硬编码 + 修复 Next.js params Promise 报错

* feat(leaderboard): add user tag and group filters for user ranking (#607)

* feat(leaderboard): add user tag and group filters for user ranking (#606)

Add filtering capability to the leaderboard user ranking by:
- userTags: filter users by their tags (OR logic)
- userGroups: filter users by their providerGroup (OR logic)

Changes:
- Repository: Add UserLeaderboardFilters interface and SQL filtering
- Cache: Extend LeaderboardFilters and include filters in cache key
- API: Parse userTags/userGroups query params (CSV format, max 20)
- Frontend: Add TagInput filters (admin-only, user scope only)
- i18n: Add translation keys for 5 languages

Closes #606

* refactor: apply reviewer suggestions for leaderboard filters

- Use JSONB ? operator instead of @> for better performance
- Extract parseListParam helper to reduce code duplication

* feat(leaderboard): add tag/group suggestions dropdown for better UX

- Fetch all user tags and groups via getAllUserTags/getAllUserKeyGroups
- Pass suggestions to TagInput for autocomplete dropdown
- Validate input against available suggestions
- Consistent with /dashboard/users filter behavior

* docs: update Privnode offer details in README files

* fix: 修复 1M 上下文标头兼容问题

* fix: resolve container name conflicts in multi-user environments (#625)

* fix: resolve container name conflicts in multi-user environments

- Add top-level `name` field with COMPOSE_PROJECT_NAME env var support
- Remove hardcoded container_name from all services
- Users can now set COMPOSE_PROJECT_NAME in .env for complete isolation

Closes #624

* fix: add top-level name field for project isolation

Add `name: ${COMPOSE_PROJECT_NAME:-claude-code-hub}` to enable
complete project isolation via environment variable.

* feat(dashboard): improve user management, statistics reset, and i18n (#610)

* fix(dashboard/logs): add reset options to filters and use short time format

- Add "All keys" SelectItem to API Key filter dropdown
- Add "All status codes" SelectItem to Status Code filter dropdown
- Use __all__ value instead of empty string (Radix Select requirement)
- Add formatDateDistanceShort() for compact time display (2h ago, 3d ago)
- Update RelativeTime component to use short format

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(providers): add Auto Sort button to dashboard and fix i18n

- Add AutoSortPriorityDialog to dashboard/providers page
- EN: "Auto Sort Priority" -> "Auto Sort"
- RU: "Авто сортировка приоритета" -> "Автосорт"
- RU: "Добавить провайдера" -> "Добавить поставщика"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(i18n): improve Russian localization and fix login errors

Russian localization improvements:
- Menu: "Управление поставщиками" -> "Поставщики"
- Menu: "Доступность" -> "Мониторинг"
- Filters: "Последние 7/30 дней" -> "7д/30д"
- Dashboard: "Статистика использования" -> "Статистика"
- Dashboard: "Показать статистику..." -> "Только ваши ключи"
- Quota: add missing translations (manageNotice, withQuotas, etc.)

Login error localization:
- Fix issue where login errors displayed in Chinese ("无效或已过期") regardless of locale
- Add locale detection from NEXT_LOCALE cookie and Accept-Language header
- Add 3 new error keys: apiKeyRequired, apiKeyInvalidOrExpired, serverError
- Support all 5 languages: EN, JA, RU, ZH-CN, ZH-TW
- Remove product name from login privacyNote for all locales

Files changed:
- messages/*/auth.json: new error keys, update privacyNote
- messages/ru/dashboard.json, messages/ru/quota.json: Russian improvements
- src/app/api/auth/login/route.ts: add getLocaleFromRequest()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat(dashboard/users): improve user management with key quotas and tokens

- Add access/model restrictions support (allowedClients/allowedModels)
- Add tokens column and refresh button to users table
- Add todayTokens calculation in repository layer (sum all token types)
- Add visual status indicators with color-coded icons (active/disabled/expiring/expired)
- Allow users to view their own key quota (was admin-only)
- Fix React Query cache invalidation on status toggle
- Fix filter logic: change tag/keyGroup from OR to AND
- Refactor time display: move formatDateDistanceShort to component with i18n
- Add fixed header/footer to key dialogs for better UX

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat(dashboard/users): add reset statistics with optimized Redis pipeline

- Implement reset all statistics functionality for admins
- Optimize Redis operations: replace sequential redis.keys() with parallel SCAN
- Add scanPattern() helper for production-safe key scanning
- Comprehensive error handling and performance metrics logging
- 50-100x performance improvement with no Redis blocking

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix(lint): apply biome formatting and fix React hooks dependencies

- Fix useEffect dependencies in RelativeTime component (wrap formatShortDistance in useCallback)
- Remove unused effectiveGroupText variable in key-row-item.tsx
- Apply consistent LF line endings across modified files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: address code review feedback from PR #610

- Remove duplicate max-h class in edit-key-dialog.tsx (keep max-h-[90dvh] only)
- Add try-catch fallback for getTranslations in login route catch block

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: translate Russian comments to English for consistency

Addresses Gemini Code Assist review feedback.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test(users): add unit tests for resetUserAllStatistics function

Cover all requirement scenarios:
- Permission check (admin-only)
- User not found handling
- Success path with DB + Redis cleanup
- Redis not ready graceful handling
- Redis partial failure warning
- scanPattern failure warning
- pipeline.exec failure error logging
- Unexpected error handling
- Empty keys list handling

10 test cases with full mock coverage.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: John Doe <johndoe@example.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

* feat(my-usage): cache statistics and timezone fixes (#623)

* feat(my-usage): add cache token statistics to model breakdown

Add cacheCreationTokens and cacheReadTokens fields to ModelBreakdownItem
interface and related DB queries for displaying cache statistics in the
statistics summary card modal.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(my-usage): use configured timezone for date filtering

Use server timezone (TZ config) instead of browser locale when:
- Filtering statistics by date (getMyStatsSummary)
- Calculating daily quota time ranges (getTimeRangeForPeriod)

This ensures consistent date interpretation across different client
timezones. Fixes discrepancy between Daily Quota and Statistics
Summary when user is in a different timezone than the server.

Added comprehensive unit tests covering:
- Date parsing in configured timezone
- Timezone offset calculations
- Day boundary edge cases

* refactor(my-usage): clean up expiration displays

Remove duplicate expiration information:
- Remove Key Expires chip from Welcome header (keep User Expires only)
- Remove "Expiring Soon" warning block from Quota Usage cards

Improve countdown display in ExpirationInfo component:
- Add Clock icon
- Increase font size to match date display
- Use monospace font for better readability
- Color-coded by status (emerald/amber/red)

ExpirationInfo remains the single source of expiration data.

* fix: correct cache hit rate formula to exclude output tokens

Output tokens are never cached (per Anthropic docs), so they should not be
included in cache hit rate calculation. Changed formula from:
  cacheReadTokens / (input + output + cacheCreate + cacheRead)
to:
  cacheReadTokens / (input + cacheCreate + cacheRead)

Affects:
- /my-usage model breakdown cache hit rate display
- /dashboard/leaderboard provider cache hit rate ranking

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: address PR #623 review feedback

- Fix outdated cache hit rate comment (leaderboard.ts:430)
- Add keyboard accessibility to model breakdown rows (a11y)
- Rename totalTokens -> totalInputTokens in ProviderCacheHitRateLeaderboardEntry
- Extract parseDateRangeInServerTimezone helper to reduce duplication

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: add backward compatibility for totalTokens field

Keep returning totalTokens (deprecated) alongside totalInputTokens
for API consumers that haven't migrated yet.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: John Doe <johndoe@example.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Set HOSTNAME environment variable in Dockerfile (#622)

Add HOSTNAME environment variable for container

* fix: inherit 1M flag from client

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* fix(my-usage): make date range DST-safe

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* test(api): cover leaderboard comma-list parsing

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(auth): avoid hardcoded server error fallback

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(actions): localize key quota permission errors

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* docs: clarify context 1M inherit behavior

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* test(settings): fix model multi-select messages loader

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* test(dashboard): provide QueryClientProvider in edit key form test

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* chore(scripts): avoid hardcoded docker container names

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(i18n): replace fullwidth parentheses in ja dashboard

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(ui): add a11y label and clean up dialog styles

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* chore: ignore tmp scratch directory

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* feat: Dashboard Logs:秒级时间筛选 + Session ID 精确筛选/联想/展示(含回归修复) (#611)

* feat: add sessionId filter for usage logs

* feat: add seconds-level time filters for logs

* feat: wire sessionId into logs URL filters

* chore: add i18n keys for logs sessionId

* feat: add sessionId column to logs tables

* feat: add sessionId suggestions for logs

* docs: document dashboard logs call chain

* test: add logs sessionId/time filter coverage config

* fix: keep sessionId search input focused

* fix: drop leaked page param on logs apply

* fix: reload sessionId suggestions on scope change

* fix: harden logs url params and time parsing

* fix: avoid keys join for sessionId suggestions

* test: strengthen empty sessionId filter assertions

* chore: format logs sessionId suggestions test

* Update src/actions/usage-logs.ts

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* feat: 补充公共静态变量 SESSION_ID_SUGGESTION_LIMIT

* fix: use prefix LIKE for sessionId suggestions

* refactor: centralize usage logs sessionId suggestion constants

* refactor: simplify logs url filters parsing

* refactor: reuse clipboard util for sessionId copy

* chore(db): add sessionId prefix index

* docs: clarify sessionId suggestion semantics

* test: add escapeLike unit tests

* chore: apply biome fixes for sessionId search

* feat: include session id in error responses

* test: add coverage suite for session id errors

* docs: add guide for error session id

* chore: format code (feat-logs-sessionid-time-filter-233f96a)

* feat(dashboard/logs): add fullscreen mode (#632)

* Fix provider exhaustion after model redirect (refs #629) (#633)

* Fix provider fallback after model redirect

Use original model for provider selection during failover and tolerate tool_result blocks in non-stream converters to prevent premature provider exhaustion. References #629.

* Add regression test for model redirect provider selection

Ensures provider selection uses original model even when current model is redirected (refs #629).

* chore: format code (fix-629-provider-fallback-toolresult-b17f671)

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

---------

Co-authored-by: YangQing-Lin <56943790+YangQing-Lin@users.noreply.github.com>
Co-authored-by: SaladDay <92240037+SaladDay@users.noreply.github.com>
Co-authored-by: miraserver <20286838+miraserver@users.noreply.github.com>
Co-authored-by: John Doe <johndoe@example.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: hwa <yll2002mail@gmail.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Ding <ding113@users.noreply.github.com>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:i18n area:statistics area:UI enhancement New feature or request size/S Small PR (< 200 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant