Skip to content

Comments

fix(ui): resolve Issue #428 & #429 - filter dropdown and pagination bugs#436

Merged
ding113 merged 1 commit intodevfrom
fix/user-selection
Dec 24, 2025
Merged

fix(ui): resolve Issue #428 & #429 - filter dropdown and pagination bugs#436
ding113 merged 1 commit intodevfrom
fix/user-selection

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Dec 24, 2025

Summary

Changes

File Description
usage-logs-filters.tsx Replace Select with Popover + Command for User/Provider filters
user.ts Fix cursor precision with to_char(..., 'US')
usage-logs.ts Same cursor precision fix
messages/*/dashboard.json Add i18n translations (5 locales)

Test plan

  • Create 100+ users and verify dropdown search/selection works
  • Create users rapidly (same millisecond) and verify pagination has no duplicates
  • Verify keyboard navigation (Tab, Enter, Escape)
  • Verify clear selection functionality
  • Verify i18n translations display correctly

Closes #428, Closes #429

Generated with Claude Code

Issue #428: User/Provider filter dropdown unusable with 100+ items
- Replace <Select> with Popover + Command (searchable combobox)
- Add search filtering, keyboard navigation, clear selection
- Add i18n translations for all 5 locales

Issue #429: User list pagination shows duplicate records
- Fix timestamp precision loss (microseconds -> milliseconds)
- Use PostgreSQL to_char() to preserve full 6-digit precision
- Apply same fix to usage-logs pagination

Files changed:
- src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx
- src/repository/user.ts
- src/repository/usage-logs.ts
- messages/*/dashboard.json (5 locales)

Closes #428, Closes #429

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@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 addresses two critical UI and data integrity issues. It significantly improves the user experience for filtering large lists of users and providers by introducing searchable dropdowns, and it resolves a data pagination bug that led to duplicate entries by ensuring precise timestamp handling in database queries.

Highlights

  • Enhanced Filter Dropdowns: Replaced static components with searchable Combobox (Popover + Command) for User and Provider filters in the usage logs dashboard, resolving usability issues with large datasets (100+ items). Improved Pagination Accuracy: Fixed an issue causing duplicate records in user list and usage log pagination by preserving microsecond precision for createdAt timestamps using PostgreSQL's to_char() function. 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. 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 area:i18n labels Dec 24, 2025
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 effectively resolves two separate issues. The UI is improved by replacing standard select dropdowns with searchable comboboxes, which enhances usability for lists with many items. The pagination bug due to timestamp precision loss is correctly fixed at the database query level. The code is well-structured. I have a couple of suggestions to optimize the new combobox components for better performance with large datasets by memoizing lookups, which aligns with the goal of this PR.

Comment on lines +287 to +294
{localFilters.userId ? (
(users.find((user) => user.id === localFilters.userId)?.name ??
localFilters.userId.toString())
) : (
<span className="text-muted-foreground">
{isUsersLoading ? t("logs.stats.loading") : t("logs.filters.allUsers")}
</span>
)}
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 current implementation uses users.find() to get the selected user's name on every render. This can be inefficient for large lists of users, which is the scenario this PR addresses. A linear search on every render could lead to performance issues.

To optimize this, you can create a Map of user IDs to names using useMemo. This will provide a more performant O(1) lookup.

Add this to your component:

const userMap = useMemo(() => new Map(users.map((user) => [user.id, user.name])), [users]);
Suggested change
{localFilters.userId ? (
(users.find((user) => user.id === localFilters.userId)?.name ??
localFilters.userId.toString())
) : (
<span className="text-muted-foreground">
{isUsersLoading ? t("logs.stats.loading") : t("logs.filters.allUsers")}
</span>
)}
{localFilters.userId ? (
userMap.get(localFilters.userId) ?? localFilters.userId.toString()
) : (
<span className="text-muted-foreground">
{isUsersLoading ? t("logs.stats.loading") : t("logs.filters.allUsers")}
</span>
)}

Comment on lines +394 to +403
{localFilters.providerId ? (
(providers.find((provider) => provider.id === localFilters.providerId)?.name ??
localFilters.providerId.toString())
) : (
<span className="text-muted-foreground">
{isProvidersLoading
? t("logs.stats.loading")
: t("logs.filters.allProviders")}
</span>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Similar to the user filter, providers.find() is used here on every render, which can be inefficient for a large number of providers. This can be optimized by using a Map for O(1) lookups.

Add this to your component:

const providerMap = useMemo(() => new Map(providers.map((provider) => [provider.id, provider.name])), [providers]);
Suggested change
{localFilters.providerId ? (
(providers.find((provider) => provider.id === localFilters.providerId)?.name ??
localFilters.providerId.toString())
) : (
<span className="text-muted-foreground">
{isProvidersLoading
? t("logs.stats.loading")
: t("logs.filters.allProviders")}
</span>
)}
{localFilters.providerId ? (
providerMap.get(localFilters.providerId) ??
localFilters.providerId.toString()
) : (
<span className="text-muted-foreground">
{isProvidersLoading
? t("logs.stats.loading")
: t("logs.filters.allProviders")}
</span>
)}

@github-actions
Copy link
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@github-actions github-actions bot added the size/M Medium PR (< 500 lines) label Dec 24, 2025
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 addresses Issues #428 and #429, implementing searchable Combobox filters and fixing cursor pagination precision. However, a critical bug was introduced that will cause runtime errors.

PR Size: M

  • Lines changed: 238 (182 additions, 56 deletions)
  • Files changed: 9

Issues Found

Category Critical High Medium Low
Logic/Bugs 1 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

Critical Issues (Must Fix)

1. [LOGIC-BUG] Variable renamed but usages not updated - ReferenceError

File: src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx:292
Confidence: 100

The variable extraKeyGroupOption is renamed to _extraKeyGroupOption in the diff:

-  const extraKeyGroupOption = useMemo(() => {
+  const _extraKeyGroupOption = useMemo(() => {

However, the template still references the old name at lines 438, 439, 443, and 445:

{extraKeyGroupOption ? (
  <SelectItem value={extraKeyGroupOption}>
    <Badge ... title={extraKeyGroupOption}>
      {extraKeyGroupOption}
    </Badge>
  </SelectItem>
) : null}

This will cause a ReferenceError: extraKeyGroupOption is not defined at runtime when the KeyEditSection component renders for non-admin users with provider groups.

Suggested fix:

// Either revert the rename (if the variable is still used):
const extraKeyGroupOption = useMemo(() => {
  // ...
}, [normalizedKeyProviderGroup, normalizedUserProviderGroup, userGroups]);

// Or rename ALL references to _extraKeyGroupOption

Other Changes Reviewed (No Issues Found)

  • usage-logs-filters.tsx: The Popover + Command implementation for User/Provider filters looks correct. Proper handling of loading states, selection clearing, and search functionality.

  • user.ts and usage-logs.ts: The cursor pagination fix using to_char(..., 'US') is a good solution to preserve microsecond precision and prevent duplicate records during pagination.

  • Translation files: All 5 locales updated consistently with matching keys.

Review Coverage

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

Automated review by Claude AI

providerGroup: users.providerGroup,
tags: users.tags,
createdAt: users.createdAt,
createdAtRaw: sql<string>`to_char(${users.createdAt}, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`,
Copy link
Contributor

Choose a reason for hiding this comment

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

[CRITICAL] [LOGIC-BUG] createdAtRaw formats timestamptz in session timezone but appends Z (UTC), breaking keyset cursors outside UTC.

Evidence (src/repository/user.ts:189):
createdAtRaw: sql<string>\to_char(${users.createdAt}, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`,`

Why this is a problem: to_char(timestamptz, ...) uses the DB session TimeZone for formatting; the literal "Z" claims the value is UTC. If TimeZone ≠ UTC, the cursor string is shifted, and (${users.createdAt}, ${users.id}) > (${cursor.createdAt}::timestamptz, ...) can skip/duplicate rows.

Suggested fix:

createdAtRaw: sql<string>`
  to_char(${users.createdAt} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')
`,

.select({
id: messageRequest.id,
createdAt: messageRequest.createdAt,
createdAtRaw: sql<string>`to_char(${messageRequest.createdAt}, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`,
Copy link
Contributor

Choose a reason for hiding this comment

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

[CRITICAL] [LOGIC-BUG] createdAtRaw formats timestamptz in session timezone but appends Z (UTC), so nextCursor can point at the wrong rows when DB TimeZone ≠ UTC.

Evidence (src/repository/usage-logs.ts:187):
createdAtRaw: sql<string>\to_char(${messageRequest.createdAt}, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`,`

Why this is a problem: to_char(timestamptz, ...) uses the connection/session TimeZone to render the timestamp, but the format hardcodes "Z". In non-UTC deployments this produces a cursor string that is not the same instant, and (${messageRequest.createdAt}, ${messageRequest.id}) < (${cursor.createdAt}::timestamptz, ...) can skip/duplicate log rows.

Suggested fix:

createdAtRaw: sql<string>`
  to_char(
    ${messageRequest.createdAt} AT TIME ZONE 'UTC',
    'YYYY-MM-DD"T"HH24:MI:SS.US"Z"'
  )
`,

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 introduces a critical correctness risk in the new keyset-cursor formatting for createdAt: the cursor string is formatted in the DB session timezone but labeled as UTC (Z). In non-UTC deployments, pagination can still skip/duplicate rows despite the intended microsecond fix.

PR Size: M

  • Lines changed: 238
  • Files changed: 9

Issues Found

Category Critical High Medium Low
Logic/Bugs 2 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

Critical Issues (Must Fix)

  • src/repository/user.ts:189 [LOGIC-BUG] to_char(timestamptz, ...) + literal "Z" can mislabel time as UTC and break cursors (Confidence: 95)
  • src/repository/usage-logs.ts:187 [LOGIC-BUG] to_char(timestamptz, ...) + literal "Z" can mislabel time as UTC and break cursors (Confidence: 95)

High Priority Issues (Should Fix)

  • None.

Review Coverage

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

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 #436 and applied label size/M (238 lines changed, 9 files).
  • Left 2 Critical inline comments on cursor timestamp formatting that can break pagination when DB TimeZone ≠ UTC: src/repository/user.ts:189 and src/repository/usage-logs.ts:187.
  • Submitted the required PR review summary via gh pr review --comment.

@ding113 ding113 merged commit ae7a3b3 into dev Dec 24, 2025
17 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Dec 24, 2025
ding113 added a commit that referenced this pull request Dec 24, 2025
- Fix to_char timezone issue in cursor pagination (user.ts:189, usage-logs.ts:187)
  - Add AT TIME ZONE 'UTC' to ensure consistent UTC output
  - Prevents cursor mismatch in non-UTC deployments
- Optimize filter dropdown lookup performance (usage-logs-filters.tsx)
  - Add userMap/providerMap useMemo for O(1) name lookup
  - Replace Array.find() with Map.get() for 100+ item scenarios

Addresses PR #436 review feedback from Codex AI

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:i18n area:UI bug Something isn't working size/M Medium PR (< 500 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant