Skip to content

feat(requests): add time-range filter for proxy request search#596

Merged
awsl233777 merged 5 commits into
mainfrom
feature/request-time-search
Jun 4, 2026
Merged

feat(requests): add time-range filter for proxy request search#596
awsl233777 merged 5 commits into
mainfrom
feature/request-time-search

Conversation

@awsl233777

@awsl233777 awsl233777 commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add startTime/endTime filter to proxy request list and count APIs, enabling users to search requests by creation time range
  • Add database migration (v16) to create idx_proxy_requests_created_at_id composite index with large-table skip safety
  • Frontend: add TimeRangeFilter component with datetime-local inputs and 400ms debounce
  • Refactor: extract applyProxyRequestFilter to eliminate duplicated filter logic in repository layer
  • Refactor: convert useInfiniteProxyRequests/useProxyRequestsCount/getProxyRequestsCount from positional args to options object (RequestFilterParams)
  • Refactor: replace fragile queryKey array-index access with structured object keys for infinite/count queries
  • Security: restrict parseTimeQuery to only accept millisecond timestamps and RFC3339 formats (reject ambiguous timezone-less formats)

Test plan

  • Go build passes
  • TypeScript type check passes (tsc --noEmit)
  • All 11 proxy request repository tests pass, including new TestProxyRequestListCursorAndCountFilterByCreatedAtRange
  • Manual: verify time range filter UI renders correctly and filters requests
  • Manual: verify WebSocket real-time updates respect time range filter
  • Manual: verify debounce prevents excessive API calls during datetime input

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 新功能
    • 请求列表新增按创建时间的起止时间筛选(可清空),筛选会同步影响列表与总数;支持 ISO 字符串或 13 位毫秒时间戳作为输入并做校验,错误或结束时间早于开始时间会返回 400。
  • 界面
    • 新增日历与弹出式时间选择器组件,微调按钮样式与间距,提升时间筛选交互。
  • 性能
    • 为请求表增加基于创建时间的索引,加速时间范围查询与分页。
  • 测试
    • 新增按创建时间区间过滤的列表与计数一致性测试。
  • 本地化
    • 添加时间筛选相关文案(开始时间/结束时间/清空时间范围)。

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

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fc2e2041-6a7f-4637-852f-46fbfd7eee3a

📥 Commits

Reviewing files that changed from the base of the PR and between 934ac89 and eac51df.

📒 Files selected for processing (2)
  • web/src/components/ui/calendar.tsx
  • web/src/pages/requests/index.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/src/components/ui/calendar.tsx
  • web/src/pages/requests/index.tsx
📜 Recent review details
⏰ 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). (4)
  • GitHub Check: playwright
  • GitHub Check: e2e
  • GitHub Check: Backend Checks
  • GitHub Check: multiinstance

📝 Walkthrough

Walkthrough

该 PR 在后端与前端全链路加入 startTime/endTime 过滤:后端新增解析与仓储时间范围约束并添加索引,前端扩展传输/Hook签名、实时缓存匹配与请求页时间筛选 UI 与文案。

Changes

时间范围过滤功能完整实现

Layer / File(s) Summary
后端过滤器数据契约
internal/repository/interfaces.go
ProxyRequestFilter 添加 StartTimeEndTime 可选字段,IsEmpty() 纳入时间字段空值判断。
后端请求参数解析
internal/handler/admin.go
新增 parseTimeQuery 并在 handleProxyRequestshandleProxyRequestsCount 中解析 startTime/endTime(支持 13 位毫秒时间戳或 RFC3339/Nano),解析错误返回 400;校验 endTime >= startTime。
后端仓储过滤应用与测试
internal/repository/sqlite/proxy_request.go, internal/repository/sqlite/proxy_request_test.go
ListCursorCountWithFilter 中新增基于 created_at 的起止条件,Count 的空过滤缓存判定改为 IsEmpty();新增测试验证列表与计数按时间范围一致。
后端数据库迁移与索引
internal/repository/sqlite/migrations.go
新增 v16 迁移,为 proxy_requests 添加 (created_at, id) 复合索引,含阈值跳过与方言幂等/回滚处理。
前端传输层与类型契约
web/src/lib/transport/types.ts, web/src/lib/transport/interface.ts, web/src/lib/transport/http-transport.ts
CursorPaginationParams 添加 startTime/endTimeTransport.getProxyRequestsCountHttpTransport.getProxyRequestsCount 增加可选 startTime/endTime 并将其写入查询参数。
前端查询 Hook 与实时匹配
web/src/hooks/queries/use-requests.ts
startTime/endTime 纳入 requestKeysuseInfiniteProxyRequestsuseProxyRequestsCount;新增 matchesRequestTimeRange,在 cursor/infinite/count 的实时更新与乐观计数增量匹配中加入时间区间判断。
前端页面 UI 与文案
web/src/pages/requests/index.tsx, web/src/locales/en.json, web/src/locales/zh.json
新增 TimeRangeFilterDateTimePickerdateToISOString 转换、页面状态与回调;将 active 时间传入 Hooks,并补充中英文文案键 timeFrom/timeTo/clearTimeRange
前端 UI 组件与依赖
web/src/components/ui/button.tsx, web/src/components/ui/calendar.tsx, web/src/components/ui/popover.tsx, web/package.json
调整按钮样式定义,新增 Calendar/CalendarDayButtonPopover 组件,并在 package.json 中加入 react-day-picker

Sequence Diagram(s)

sequenceDiagram
  participant RequestsPage
  participant AdminHandler
  participant Repository
  participant SQLite
  RequestsPage->>AdminHandler: GET /requests or /requests/count (startTime,endTime,...)
  AdminHandler->>AdminHandler: parseTimeQuery(startTime/endTime)
  AdminHandler->>Repository: ListCursor/CountWithFilter(filter with StartTime/EndTime)
  Repository->>SQLite: SELECT ... WHERE created_at >= ? AND created_at <= ?
  SQLite-->>Repository: rows / count
  Repository-->>AdminHandler: filtered results / count
  AdminHandler-->>RequestsPage: 200 OK (JSON)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • awsl-project/maxx#339: 与本 PR 在代理请求的过滤链路(handler + repository + List/Count)上有重叠改动(另一个 PR 添加了 projectId 过滤)。
  • awsl-project/maxx#185: 修改了 use-requests 中的缓存匹配/更新逻辑,本次改动将时间范围纳入该匹配流程。
  • awsl-project/maxx#218: 与请求列表 infinite-flow 改造相关,涉及 query key 与实时缓存同步逻辑。

Suggested reviewers

  • SurviveM
  • dreamhunter2333
  • ymkiux

Poem

🐰 我把毫秒数和 RFC 编成歌,
在时间窗里数着请求的多与寡;
后端索引悄立成行,筛选稳又霍;
前端日历轻点,界面安放光华;
小兔一跳,数据与时间握手啦。

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Pull request title clearly describes the main change: adding time-range filtering capability to proxy request search functionality.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/request-time-search

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/src/pages/requests/index.tsx (1)

435-454: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

切换用户上下文时未重置时间筛选,存在跨账号残留。

这里重置了其它筛选项,但没有清理 startDateTime/endDateTime(及 debounced 状态)。切换账号后会继续沿用上一账号的时间窗口,和当前逻辑不一致。

✅ 建议修复
   setSelectedProjectId(
     readStoredNumberWithLegacy(projectFilterStorageKey, REQUEST_PROJECT_FILTER_STORAGE_KEY),
   );
   setSelectedStatus(undefined);
+  setStartDateTime('');
+  setEndDateTime('');
+  setDebouncedStartDateTime('');
+  setDebouncedEndDateTime('');
 }, [
   filterModeStorageKey,
   projectFilterStorageKey,
   providerFilterStorageKey,
   tokenFilterStorageKey,
 ]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/pages/requests/index.tsx` around lines 435 - 454, The useEffect that
runs migrateLegacyRequestFilters and resets other filters must also clear the
time filters to avoid cross-account retention; inside the same effect (the one
calling migrateLegacyRequestFilters and setSelectedProviderId/... etc.) call the
time-related setters to reset both the raw and debounced values — e.g. invoke
setStartDateTime(undefined) and setEndDateTime(undefined) and also reset the
debounced counterparts (setDebouncedStartDateTime and setDebouncedEndDateTime)
so the previous account's time window is fully cleared when switching context.
🧹 Nitpick comments (1)
web/src/lib/transport/interface.ts (1)

132-139: ⚡ Quick win

建议抽取统一的请求过滤类型,避免三处签名漂移。

这里和 web/src/lib/transport/http-transport.tsweb/src/hooks/queries/use-requests.ts 都在重复声明同一组过滤字段。建议在 web/src/lib/transport/types.ts 导出一个共享类型并统一引用,减少后续新增筛选项时的漏改风险。

♻️ 建议改法
// web/src/lib/transport/types.ts
+export type RequestFilterParams = Pick<
+  CursorPaginationParams,
+  'providerId' | 'status' | 'apiTokenId' | 'projectId' | 'startTime' | 'endTime'
+>;
// web/src/lib/transport/interface.ts
+import type { RequestFilterParams } from './types';
 ...
-  getProxyRequestsCount(filter?: {
-    providerId?: number;
-    status?: string;
-    apiTokenId?: number;
-    projectId?: number;
-    startTime?: string;
-    endTime?: string;
-  }): Promise<number>;
+  getProxyRequestsCount(filter?: RequestFilterParams): Promise<number>;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/lib/transport/interface.ts` around lines 132 - 139, Extract the
repeated filter object into a shared type (e.g., ProxyRequestsFilter) exported
from web/src/lib/transport/types.ts and update all signatures to use it: replace
the inline filter in getProxyRequestsCount (interface in
web/src/lib/transport/interface.ts), the corresponding parameter in
web/src/lib/transport/http-transport.ts, and the filter type in
web/src/hooks/queries/use-requests.ts to import and reference the new
ProxyRequestsFilter type so all three sites share the same definition.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/handler/admin.go`:
- Around line 893-896: parseTimeQuery currently treats any integer string as a
millisecond timestamp (e.g., "1717488000" seconds becomes 1970), so modify
parseTimeQuery to strictly accept only millisecond-precision integers: before
calling strconv.ParseInt(...) or before accepting its result, validate the raw
string has millisecond precision (e.g., at least 13 digits for non-negative
timestamps or appropriate length for negatives) or check the parsed value is
within a reasonable ms-range (|value| >= 1e12) and reject/return an error
otherwise; update the branch that returns time.UnixMilli(millis) (the code
around parseTimeQuery) to perform this check and return a parse error for
second-precision integers.

In `@web/src/hooks/queries/use-requests.ts`:
- Around line 38-49: matchesRequestFilter currently parses
filter.startTime/filter.endTime using new Date(...).getTime(), which yields NaN
for pure-millisecond strings; update parsing so that in matchesRequestFilter you
first detect numeric-only strings for filter.startTime and filter.endTime (e.g.
/^\d+$/) and treat them as millisecond timestamps via Number(...) (or parseInt),
otherwise fall back to Date.parse(...) (or new Date(...).getTime()); then use
those parsed millisecond values for the existing comparisons against createdAtMs
(keep createdAtMs = new Date(request.createdAt).getTime() and guard with
Number.isFinite as before). Ensure you handle undefined startTime/endTime
exactly as current logic.

---

Outside diff comments:
In `@web/src/pages/requests/index.tsx`:
- Around line 435-454: The useEffect that runs migrateLegacyRequestFilters and
resets other filters must also clear the time filters to avoid cross-account
retention; inside the same effect (the one calling migrateLegacyRequestFilters
and setSelectedProviderId/... etc.) call the time-related setters to reset both
the raw and debounced values — e.g. invoke setStartDateTime(undefined) and
setEndDateTime(undefined) and also reset the debounced counterparts
(setDebouncedStartDateTime and setDebouncedEndDateTime) so the previous
account's time window is fully cleared when switching context.

---

Nitpick comments:
In `@web/src/lib/transport/interface.ts`:
- Around line 132-139: Extract the repeated filter object into a shared type
(e.g., ProxyRequestsFilter) exported from web/src/lib/transport/types.ts and
update all signatures to use it: replace the inline filter in
getProxyRequestsCount (interface in web/src/lib/transport/interface.ts), the
corresponding parameter in web/src/lib/transport/http-transport.ts, and the
filter type in web/src/hooks/queries/use-requests.ts to import and reference the
new ProxyRequestsFilter type so all three sites share the same definition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0f3d35f7-7895-4aba-8da8-3ae17c68e83a

📥 Commits

Reviewing files that changed from the base of the PR and between aec5b9b and 7b65f3a.

📒 Files selected for processing (12)
  • internal/handler/admin.go
  • internal/repository/interfaces.go
  • internal/repository/sqlite/migrations.go
  • internal/repository/sqlite/proxy_request.go
  • internal/repository/sqlite/proxy_request_test.go
  • web/src/hooks/queries/use-requests.ts
  • web/src/lib/transport/http-transport.ts
  • web/src/lib/transport/interface.ts
  • web/src/lib/transport/types.ts
  • web/src/locales/en.json
  • web/src/locales/zh.json
  • web/src/pages/requests/index.tsx
📜 Review details
⏰ 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). (2)
  • GitHub Check: playwright
  • GitHub Check: e2e
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: ymkiux
Repo: awsl-project/maxx PR: 0
File: :0-0
Timestamp: 2026-03-30T09:23:52.570Z
Learning: In awsl-project/maxx PR `#466`, all 5 concerns raised in the initial review were incorrect. The `APITokenConcurrencySection` useEffect guard was present, `AcquireConcurrency` correctly returns `ErrInvalidToken` for zero-ID+empty-token, `ResolveToken` is fully tested, `IsStreamRequest` has no dependency on clientType, and passing `""` as ClientType in `ExtractToken` is intentional to scan all auth headers. Be more careful to read the actual code before flagging concerns.
🔇 Additional comments (9)
internal/repository/interfaces.go (1)

125-139: LGTM!

internal/handler/admin.go (1)

955-959: LGTM!

Also applies to: 982-986

internal/repository/sqlite/proxy_request.go (1)

94-117: LGTM!

Also applies to: 135-136, 177-178, 183-184

internal/repository/sqlite/proxy_request_test.go (1)

10-10: LGTM!

Also applies to: 191-240

internal/repository/sqlite/migrations.go (1)

489-528: LGTM!

web/src/lib/transport/types.ts (1)

376-379: LGTM!

web/src/lib/transport/http-transport.ts (1)

414-443: LGTM!

web/src/locales/en.json (1)

175-177: LGTM!

web/src/locales/zh.json (1)

174-176: LGTM!

Comment thread internal/handler/admin.go
Comment thread web/src/hooks/queries/use-requests.ts Outdated
Comment on lines +38 to +49
function matchesRequestFilter(request: ProxyRequest, filter?: RequestFilterParams): boolean {
if (!filter) return true;
if (filter.providerId !== undefined && request.providerID !== filter.providerId) return false;
if (filter.status !== undefined && request.status !== filter.status) return false;
if (filter.apiTokenId !== undefined && request.apiTokenID !== filter.apiTokenId) return false;
if (filter.projectId !== undefined && request.projectID !== filter.projectId) return false;
const createdAtMs = new Date(request.createdAt).getTime();
if (!Number.isFinite(createdAtMs)) return true;
if (filter.startTime !== undefined && createdAtMs < new Date(filter.startTime).getTime())
return false;
if (filter.endTime !== undefined && createdAtMs > new Date(filter.endTime).getTime())
return false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 验证当前运行时对“毫秒时间戳字符串”的解析行为
node - <<'NODE'
const samples = ['2026-06-04T11:05:04Z', '1717488000000'];
for (const s of samples) {
  const viaString = new Date(s).getTime();
  const viaNumber = new Date(Number(s)).getTime();
  console.log(`${s}\n  new Date(string): ${Number.isFinite(viaString) ? viaString : 'NaN'}\n  new Date(Number): ${Number.isFinite(viaNumber) ? viaNumber : 'NaN'}`);
}
NODE

Repository: awsl-project/maxx

Length of output: 212


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "RequestFilterParams|startTime|endTime|createdAt" web/src/hooks/queries/use-requests.ts
echo "----"
rg -n "startTime|endTime" web/src/lib/transport/types.ts
echo "----"
sed -n '1,120p' web/src/hooks/queries/use-requests.ts
echo "----"
sed -n '1,220p' web/src/lib/transport/types.ts

Repository: awsl-project/maxx

Length of output: 11182


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "ISO|毫秒|timestamp|ms string|startTime|endTime" web/src/lib/transport/types.ts
echo "----"
rg -n "getProxyRequestsCount|getProxyRequests|getProxyRequests\\(" -S web/src/lib/transport
echo "----"
rg -n "startTime.*endTime|endTime.*startTime" -S web/src/lib/transport

Repository: awsl-project/maxx

Length of output: 914


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '340,430p' web/src/lib/transport/types.ts

echo "---- interface.ts ----"
sed -n '90,170p' web/src/lib/transport/interface.ts

echo "---- http-transport.ts ----"
sed -n '360,480p' web/src/lib/transport/http-transport.ts

Repository: awsl-project/maxx

Length of output: 9340


修复时间过滤解析以匹配后端契约(startTime/endTime 支持“毫秒时间戳字符串”)
web/src/hooks/queries/use-requests.tsmatchesRequestFilternew Date(filter.startTime/endTime).getTime() 解析 startTime/endTime;但 web/src/lib/transport/types.ts 明确约定它们可以是“ISO 字符串或毫秒时间戳字符串”,且 http-transport.ts 会将该字符串原样透传到后端。运行时对纯数字毫秒串(如 1717488000000new Date(str) 会得到 NaN,从而使起止时间比较退化为“不生效过滤”,导致实时列表/计数与后端结果不一致。

需要在前端将 startTime/endTime 按“纯数字 => 当作毫秒时间戳;否则 => ISO/Date.parse”解析成 ms 再比较。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/hooks/queries/use-requests.ts` around lines 38 - 49,
matchesRequestFilter currently parses filter.startTime/filter.endTime using new
Date(...).getTime(), which yields NaN for pure-millisecond strings; update
parsing so that in matchesRequestFilter you first detect numeric-only strings
for filter.startTime and filter.endTime (e.g. /^\d+$/) and treat them as
millisecond timestamps via Number(...) (or parseInt), otherwise fall back to
Date.parse(...) (or new Date(...).getTime()); then use those parsed millisecond
values for the existing comparisons against createdAtMs (keep createdAtMs = new
Date(request.createdAt).getTime() and guard with Number.isFinite as before).
Ensure you handle undefined startTime/endTime exactly as current logic.

… + add 13-digit ms validation

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
web/src/hooks/queries/use-requests.ts (1)

28-44: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

修复时间过滤解析以支持毫秒时间戳字符串

matchesRequestTimeRange 使用 new Date(startTime).getTime()new Date(endTime).getTime() 解析时间参数,但根据传输层契约(web/src/lib/transport/types.ts),startTime/endTime 可以是"ISO 字符串或毫秒时间戳字符串"。

当传入纯数字毫秒串(如 "1717488000000")时,new Date("1717488000000") 会返回 Invalid Date,导致 getTime() 得到 NaN,使时间范围比较失效,从而导致实时缓存匹配逻辑与后端结果不一致。

需要在解析前判断:如果是纯数字字符串(/^\d+$/),则作为毫秒时间戳 Number(startTime);否则使用 new Date(startTime).getTime()

🛠️ 修复示例
 function matchesRequestTimeRange(
   request: ProxyRequest,
   startTime?: string,
   endTime?: string,
 ): boolean {
   const createdAtMs = new Date(request.createdAt).getTime();
   if (!Number.isFinite(createdAtMs)) {
     return true;
   }
-  if (startTime !== undefined && createdAtMs < new Date(startTime).getTime()) {
+  if (startTime !== undefined) {
+    const startMs = /^\d+$/.test(startTime) ? Number(startTime) : new Date(startTime).getTime();
+    if (Number.isFinite(startMs) && createdAtMs < startMs) {
+      return false;
+    }
+  }
+  if (endTime !== undefined) {
+    const endMs = /^\d+$/.test(endTime) ? Number(endTime) : new Date(endTime).getTime();
+    if (Number.isFinite(endMs) && createdAtMs > endMs) {
+      return false;
+    }
-    return false;
   }
-  if (endTime !== undefined && createdAtMs > new Date(endTime).getTime()) {
-    return false;
-  }
   return true;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/hooks/queries/use-requests.ts` around lines 28 - 44,
matchesRequestTimeRange currently uses new Date(...).getTime() for
startTime/endTime which fails for pure-millisecond strings; update the function
to parse startTime and endTime into startMs/endMs by first checking if the
string matches /^\d+$/ and using Number(value) in that case, otherwise use
Date.parse(value) (or new Date(value).getTime()), and only perform comparisons
if the parsed ms is finite; reference the function matchesRequestTimeRange and
the parameters startTime/endTime when making this change and ensure invalid
parses are treated as "no bound" (skip that comparison) so behavior remains
consistent with backend.
🧹 Nitpick comments (1)
internal/repository/sqlite/proxy_request.go (1)

110-130: 🏗️ Heavy lift

过滤逻辑重复,建议提取为辅助函数

ListCursor(110-130 行)与 CountWithFilter(178-198 行)中的过滤条件应用逻辑完全相同(ProviderID、Status、APITokenID、ProjectID、StartTime、EndTime 六个字段的 if filter.X != nil 判断与 Where 调用)。若后续新增过滤字段或调整过滤逻辑,需要同时修改两处,容易产生不一致。

建议将过滤逻辑提取为辅助函数(如 applyProxyRequestFilter(query *gorm.DB, filter *repository.ProxyRequestFilter) *gorm.DB),在两处调用点统一使用,避免维护时遗漏。

Also applies to: 178-198

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/repository/sqlite/proxy_request.go` around lines 110 - 130,
ListCursor and CountWithFilter duplicate the same filter-applying logic for
repository.ProxyRequestFilter (checks for ProviderID, Status, APITokenID,
ProjectID, StartTime, EndTime); extract this into a helper like
applyProxyRequestFilter(query *gorm.DB, filter *repository.ProxyRequestFilter)
*gorm.DB that encapsulates all the if filter.X != nil -> query.Where(...) and
timestamp conversions (toTimestamp), then replace the inline blocks in
ListCursor and CountWithFilter with calls to applyProxyRequestFilter to ensure a
single source of truth for filtering.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@web/src/hooks/queries/use-requests.ts`:
- Around line 28-44: matchesRequestTimeRange currently uses new
Date(...).getTime() for startTime/endTime which fails for pure-millisecond
strings; update the function to parse startTime and endTime into startMs/endMs
by first checking if the string matches /^\d+$/ and using Number(value) in that
case, otherwise use Date.parse(value) (or new Date(value).getTime()), and only
perform comparisons if the parsed ms is finite; reference the function
matchesRequestTimeRange and the parameters startTime/endTime when making this
change and ensure invalid parses are treated as "no bound" (skip that
comparison) so behavior remains consistent with backend.

---

Nitpick comments:
In `@internal/repository/sqlite/proxy_request.go`:
- Around line 110-130: ListCursor and CountWithFilter duplicate the same
filter-applying logic for repository.ProxyRequestFilter (checks for ProviderID,
Status, APITokenID, ProjectID, StartTime, EndTime); extract this into a helper
like applyProxyRequestFilter(query *gorm.DB, filter
*repository.ProxyRequestFilter) *gorm.DB that encapsulates all the if filter.X
!= nil -> query.Where(...) and timestamp conversions (toTimestamp), then replace
the inline blocks in ListCursor and CountWithFilter with calls to
applyProxyRequestFilter to ensure a single source of truth for filtering.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e1ae0e98-9661-4ea4-8b18-07f80ca97c4f

📥 Commits

Reviewing files that changed from the base of the PR and between 7b65f3a and 07109b7.

📒 Files selected for processing (6)
  • internal/handler/admin.go
  • internal/repository/sqlite/proxy_request.go
  • web/src/hooks/queries/use-requests.ts
  • web/src/lib/transport/http-transport.ts
  • web/src/lib/transport/interface.ts
  • web/src/pages/requests/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/handler/admin.go
📜 Review details
⏰ 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: multiinstance
  • GitHub Check: playwright
  • GitHub Check: e2e
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: ymkiux
Repo: awsl-project/maxx PR: 0
File: :0-0
Timestamp: 2026-03-30T09:23:52.570Z
Learning: In awsl-project/maxx PR `#466`, all 5 concerns raised in the initial review were incorrect. The `APITokenConcurrencySection` useEffect guard was present, `AcquireConcurrency` correctly returns `ErrInvalidToken` for zero-ID+empty-token, `ResolveToken` is fully tested, `IsStreamRequest` has no dependency on clientType, and passing `""` as ClientType in `ExtractToken` is intentional to scan all auth headers. Be more careful to read the actual code before flagging concerns.
📚 Learning: 2026-03-30T09:23:52.570Z
Learnt from: ymkiux
Repo: awsl-project/maxx PR: 0
File: :0-0
Timestamp: 2026-03-30T09:23:52.570Z
Learning: In awsl-project/maxx PR `#466`, all 5 concerns raised in the initial review were incorrect. The `APITokenConcurrencySection` useEffect guard was present, `AcquireConcurrency` correctly returns `ErrInvalidToken` for zero-ID+empty-token, `ResolveToken` is fully tested, `IsStreamRequest` has no dependency on clientType, and passing `""` as ClientType in `ExtractToken` is intentional to scan all auth headers. Be more careful to read the actual code before flagging concerns.

Applied to files:

  • web/src/hooks/queries/use-requests.ts
🔇 Additional comments (5)
internal/repository/sqlite/proxy_request.go (1)

172-172: LGTM!

web/src/lib/transport/interface.ts (1)

132-139: LGTM!

web/src/lib/transport/http-transport.ts (1)

414-443: LGTM!

web/src/pages/requests/index.tsx (2)

75-84: LGTM!

Also applies to: 252-256


1485-1534: LGTM!

…/endTime in both handlers

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/handler/admin.go (1)

901-955: ⚖️ Poor tradeoff

考虑提取重复的过滤参数解析逻辑。

handleProxyRequests (lines 901-955) 和 handleProxyRequestsCount (lines 978-1031) 中有约 55 行几乎完全相同的过滤参数解析代码。虽然 commit message 表明这是有意内联的(回退了之前的提取),但重复逻辑会增加维护成本:未来新增过滤参数或修改错误消息时需要在两处同步修改。

♻️ 可选重构方案:提取为辅助函数
// parseProxyRequestFilter 从查询参数中解析代理请求过滤器
func parseProxyRequestFilter(query url.Values) (*repository.ProxyRequestFilter, error) {
	providerIDStr := query.Get("providerId")
	statusStr := query.Get("status")
	apiTokenIDStr := query.Get("apiTokenId")
	projectIDStr := query.Get("projectId")
	startTimeStr := query.Get("startTime")
	endTimeStr := query.Get("endTime")

	if providerIDStr == "" && statusStr == "" && apiTokenIDStr == "" && projectIDStr == "" && startTimeStr == "" && endTimeStr == "" {
		return nil, nil
	}

	filter := &repository.ProxyRequestFilter{}
	
	if providerIDStr != "" {
		providerID, err := strconv.ParseUint(providerIDStr, 10, 64)
		if err != nil {
			return nil, errors.New("invalid providerId")
		}
		filter.ProviderID = &providerID
	}
	if statusStr != "" {
		filter.Status = &statusStr
	}
	if apiTokenIDStr != "" {
		apiTokenID, err := strconv.ParseUint(apiTokenIDStr, 10, 64)
		if err != nil {
			return nil, errors.New("invalid apiTokenId")
		}
		filter.APITokenID = &apiTokenID
	}
	if projectIDStr != "" {
		projectID, err := strconv.ParseUint(projectIDStr, 10, 64)
		if err != nil {
			return nil, errors.New("invalid projectId")
		}
		filter.ProjectID = &projectID
	}
	if startTimeStr != "" {
		startTime, err := parseTimeQuery(startTimeStr, "startTime")
		if err != nil {
			return nil, err
		}
		filter.StartTime = startTime
	}
	if endTimeStr != "" {
		endTime, err := parseTimeQuery(endTimeStr, "endTime")
		if err != nil {
			return nil, err
		}
		filter.EndTime = endTime
	}
	
	return filter, nil
}

然后在两个 handler 中简化为:

// 在 handleProxyRequests 中
filter, err := parseProxyRequestFilter(r.URL.Query())
if err != nil {
	writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
	return
}

// 在 handleProxyRequestsCount 中
filter, err := parseProxyRequestFilter(r.URL.Query())
if err != nil {
	writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
	return
}

Also applies to: 978-1031

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/handler/admin.go` around lines 901 - 955, Duplicate query-parameter
parsing for proxy request filters appears in handleProxyRequests and
handleProxyRequestsCount; extract the logic into a helper like
parseProxyRequestFilter(query url.Values) that returns
(*repository.ProxyRequestFilter, error) and use it from both handlers, returning
HTTP 400 on error (same behavior as current inline code). Ensure
parseProxyRequestFilter implements the same parsing/validation for providerId,
status, apiTokenId, projectId, startTime and endTime (using strconv.ParseUint
and parseTimeQuery) and preserves nil when no filter params are present, then
replace the inline blocks in handleProxyRequests and handleProxyRequestsCount
with calls to this function and unified error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/handler/admin.go`:
- Around line 901-955: Duplicate query-parameter parsing for proxy request
filters appears in handleProxyRequests and handleProxyRequestsCount; extract the
logic into a helper like parseProxyRequestFilter(query url.Values) that returns
(*repository.ProxyRequestFilter, error) and use it from both handlers, returning
HTTP 400 on error (same behavior as current inline code). Ensure
parseProxyRequestFilter implements the same parsing/validation for providerId,
status, apiTokenId, projectId, startTime and endTime (using strconv.ParseUint
and parseTimeQuery) and preserves nil when no filter params are present, then
replace the inline blocks in handleProxyRequests and handleProxyRequestsCount
with calls to this function and unified error handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49f703f6-ff0d-444e-9d2d-8d337410ade8

📥 Commits

Reviewing files that changed from the base of the PR and between 07109b7 and 9b7f932.

📒 Files selected for processing (1)
  • internal/handler/admin.go
📜 Review details
⏰ 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: multiinstance
  • GitHub Check: playwright
  • GitHub Check: e2e
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: ymkiux
Repo: awsl-project/maxx PR: 0
File: :0-0
Timestamp: 2026-03-30T09:23:52.570Z
Learning: In awsl-project/maxx PR `#466`, all 5 concerns raised in the initial review were incorrect. The `APITokenConcurrencySection` useEffect guard was present, `AcquireConcurrency` correctly returns `ErrInvalidToken` for zero-ID+empty-token, `ResolveToken` is fully tested, `IsStreamRequest` has no dependency on clientType, and passing `""` as ClientType in `ExtractToken` is intentional to scan all auth headers. Be more careful to read the actual code before flagging concerns.
📚 Learning: 2026-03-30T09:23:52.570Z
Learnt from: ymkiux
Repo: awsl-project/maxx PR: 0
File: :0-0
Timestamp: 2026-03-30T09:23:52.570Z
Learning: In awsl-project/maxx PR `#466`, all 5 concerns raised in the initial review were incorrect. The `APITokenConcurrencySection` useEffect guard was present, `AcquireConcurrency` correctly returns `ErrInvalidToken` for zero-ID+empty-token, `ResolveToken` is fully tested, `IsStreamRequest` has no dependency on clientType, and passing `""` as ClientType in `ExtractToken` is intentional to scan all auth headers. Be more careful to read the actual code before flagging concerns.

Applied to files:

  • internal/handler/admin.go
🔇 Additional comments (3)
internal/handler/admin.go (3)

828-848: LGTM!


901-955: LGTM!


978-1031: LGTM!

…ilter to Calendar popover

- Add startTime <= endTime validation in both handlers
- Replace raw datetime-local inputs with shadcn Popover + Calendar + time input
- Install shadcn calendar and popover components

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

Copy link
Copy Markdown
Collaborator Author

Test Screenshots

1. All requests (no filter)

pr-ss-1-all

2. Calendar popover (date + time picker)

pr-ss-2-calendar

3. Start time filter applied (06/03)

pr-ss-3-startfilter

4. Time range filter (06/03 - 06/04)

pr-ss-4-range

5. After clearing time range filter

pr-ss-5-cleared

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/src/pages/requests/index.tsx`:
- Line 50: 移除未使用的 Button 导入:在 web/src/pages/requests/index.tsx 中找到导入列表并删除未使用的标识符
Button(即从 import 列表中移除 "Button"),保存后重新运行 lint/build 以确认 ESLint/TS6133 错误已解决。
- Around line 1525-1530: The Calendar usage is passing a removed prop
initialFocus to DayPicker which causes TS2322; update the Calendar invocation
(the Calendar component that forwards DayPicker props) to remove initialFocus
and replace it with autoFocus (or drop it), i.e., change occurrences of
initialFocus on the Calendar component to autoFocus so the forwarded props match
react-day-picker@10's API and restore initial focus behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a939b2f5-9edd-41b2-80ad-a0a4de24d147

📥 Commits

Reviewing files that changed from the base of the PR and between 9b7f932 and 934ac89.

⛔ Files ignored due to path filters (1)
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • internal/handler/admin.go
  • web/package.json
  • web/src/components/ui/button.tsx
  • web/src/components/ui/calendar.tsx
  • web/src/components/ui/popover.tsx
  • web/src/pages/requests/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/handler/admin.go
📜 Review details
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: ymkiux
Repo: awsl-project/maxx PR: 0
File: :0-0
Timestamp: 2026-03-30T09:23:52.570Z
Learning: In awsl-project/maxx PR `#466`, all 5 concerns raised in the initial review were incorrect. The `APITokenConcurrencySection` useEffect guard was present, `AcquireConcurrency` correctly returns `ErrInvalidToken` for zero-ID+empty-token, `ResolveToken` is fully tested, `IsStreamRequest` has no dependency on clientType, and passing `""` as ClientType in `ExtractToken` is intentional to scan all auth headers. Be more careful to read the actual code before flagging concerns.
📚 Learning: 2026-03-14T03:21:15.610Z
Learnt from: ymkiux
Repo: awsl-project/maxx PR: 0
File: :0-0
Timestamp: 2026-03-14T03:21:15.610Z
Learning: In the awsl-project/maxx repository, `AlertDialogAction` in `web/src/components/ui/alert-dialog.tsx` is a custom wrapper around `<Button {...props}>` (not the standard shadcn/ui implementation). It accepts `React.ComponentProps<typeof Button>` and spreads all props including `variant` directly into `<Button>`, so variant styling is correctly forwarded at runtime.

Applied to files:

  • web/src/components/ui/calendar.tsx
  • web/src/components/ui/button.tsx
🪛 GitHub Actions: E2E Multi-instance / 0_multiinstance.txt
web/src/pages/requests/index.tsx

[error] 50-50: TypeScript (TS6133) failed during build: 'Button' is declared but its value is never read.


[error] 1529-1529: TypeScript (TS2322) failed during build: 'initialFocus' prop does not exist on type for DayPicker (Property 'initialFocus' does not exist in DayPickerProps).

web/src/components/ui/calendar.tsx

[error] 88-88: TypeScript (TS2353) failed during build: Object literal may only specify known properties, and 'table' does not exist in type 'Partial'.

🪛 GitHub Actions: E2E Multi-instance / multiinstance
web/src/pages/requests/index.tsx

[error] 50-50: TypeScript (tsc) TS6133: 'Button' is declared but its value is never read.


[error] 1529-1529: TypeScript (tsc) TS2322: Props type mismatch for DayPicker. Property 'initialFocus' does not exist on the expected DayPicker props type.

web/src/components/ui/calendar.tsx

[error] 88-88: TypeScript (tsc) TS2353: Object literal may only specify known properties, and 'table' does not exist in type 'Partial'.

🪛 GitHub Actions: E2E Playwright Tests / 0_playwright.txt
web/src/components/ui/calendar.tsx

[error] 88-88: Build failed: TypeScript error TS2353 — Object literal may only specify known properties, and 'table' does not exist in type 'Partial'.

🪛 GitHub Actions: E2E Playwright Tests / playwright
web/src/components/ui/calendar.tsx

[error] 88-88: TypeScript build failed (tsc). TS2353: Object literal may only specify known properties, and 'table' does not exist in type 'Partial'.

🪛 GitHub Actions: PR Checks / 1_Frontend Checks.txt
web/src/pages/requests/index.tsx

[error] 50-50: ESLint (@typescript-eslint/no-unused-vars): 'Button' is defined but never used.

🪛 GitHub Actions: PR Checks / Frontend Checks
web/src/pages/requests/index.tsx

[error] 50-50: ESLint (@typescript-eslint/no-unused-vars): 'Button' is defined but never used.

🪛 GitHub Check: Frontend Checks
web/src/pages/requests/index.tsx

[failure] 50-50:
'Button' is defined but never used

🔇 Additional comments (4)
web/src/components/ui/button.tsx (1)

1-59: LGTM!

web/src/components/ui/popover.tsx (1)

1-89: LGTM!

web/package.json (1)

49-49: LGTM!

web/src/components/ui/calendar.tsx (1)

88-88: ⚡ Quick win

react-day-picker v10 中 classNames.table 非法,需改用对应的网格样式键

Line 88table: "w-full border-collapse" 会触发 TS2353:table 不在 Partial<ClassNames>,构建阻塞;按 v10 的 ClassNames 类型把该键替换为类似 month_grid 的合法键。

建议修复
-        table: "w-full border-collapse",
+        month_grid: "w-full border-collapse",

Comment thread web/src/pages/requests/index.tsx Outdated
Comment thread web/src/pages/requests/index.tsx
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@awsl233777 awsl233777 force-pushed the feature/request-time-search branch from b30cf0a to eac51df Compare June 4, 2026 16:25
@awsl233777 awsl233777 merged commit 7964367 into main Jun 4, 2026
6 checks passed
@awsl233777 awsl233777 deleted the feature/request-time-search branch June 4, 2026 16:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant