Skip to content

Commit 0b114e9

Browse files
committed
feat: 增加年度统计范围
1 parent ff03c0d commit 0b114e9

5 files changed

Lines changed: 59 additions & 1 deletion

File tree

backend/internal/api/dashboard_handler.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ func resolveDashboardRange(now time.Time, rangeKey string) (time.Time, time.Time
193193
end := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
194194
start := end.AddDate(0, 0, -30)
195195
return start.UTC(), end.UTC(), 24 * time.Hour
196+
case "1y":
197+
end := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
198+
start := end.AddDate(-1, 0, 0)
199+
return start.UTC(), end.UTC(), 24 * time.Hour
196200
default:
197201
start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
198202
end := start.Add(24 * time.Hour)

backend/internal/api/dashboard_handler_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,46 @@ func TestDashboardHandlerBuildsCalendarAlignedFilter(t *testing.T) {
171171
}
172172
}
173173

174+
func TestDashboardHandlerBuildsYearlyCalendarAlignedFilter(t *testing.T) {
175+
t.Setenv("TZ", "Asia/Shanghai")
176+
now := time.Date(2026, 6, 16, 9, 30, 0, 0, time.FixedZone("CST", 8*3600))
177+
previousLocal := time.Local
178+
time.Local = now.Location()
179+
defer func() {
180+
time.Local = previousLocal
181+
}()
182+
183+
stub := &dashboardUsageStub{
184+
summary: usage.EventSummary{RequestCount: 1},
185+
}
186+
handler := api.NewDashboardHandler(stub)
187+
188+
req := httptest.NewRequest(http.MethodGet, "/dashboard/summary?range=1y", nil)
189+
rec := httptest.NewRecorder()
190+
191+
restore := api.SetDashboardNowForTest(func() time.Time { return now })
192+
defer restore()
193+
194+
handler.ServeHTTP(rec, req)
195+
if rec.Code != http.StatusOK {
196+
t.Fatalf("GET /dashboard/summary status = %d, want %d", rec.Code, http.StatusOK)
197+
}
198+
if stub.lastFilter.From == nil || stub.lastFilter.To == nil {
199+
t.Fatalf("expected filter bounds to be set")
200+
}
201+
wantFrom := time.Date(2025, 6, 16, 16, 0, 0, 0, time.UTC)
202+
wantTo := time.Date(2026, 6, 16, 16, 0, 0, 0, time.UTC)
203+
if !stub.lastFilter.From.Equal(wantFrom) || !stub.lastFilter.To.Equal(wantTo) {
204+
t.Fatalf("filter bounds = %v..%v, want %v..%v", stub.lastFilter.From, stub.lastFilter.To, wantFrom, wantTo)
205+
}
206+
if stub.lastFilter.BucketSize != 24*time.Hour {
207+
t.Fatalf("BucketSize = %v, want %v", stub.lastFilter.BucketSize, 24*time.Hour)
208+
}
209+
if stub.lastFilter.BucketLocation != time.Local {
210+
t.Fatalf("BucketLocation = %v, want time.Local", stub.lastFilter.BucketLocation)
211+
}
212+
}
213+
174214
func TestDashboardHandlerBuildsServerUserFilter(t *testing.T) {
175215
t.Parallel()
176216

frontend/src/features/stats/StatsPage.test.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,17 @@ describe("StatsPage", () => {
130130
expect(getDashboardRecentEvents).toHaveBeenLastCalledWith("24h", undefined, "", 20, 11);
131131
});
132132
});
133+
134+
it("loads yearly dashboard statistics when the yearly range is selected", async () => {
135+
render(<StatsPage language="zh-CN" t={t} />);
136+
137+
fireEvent.click(await screen.findByText("1y"));
138+
139+
await waitFor(() => {
140+
expect(getDashboardSummary).toHaveBeenLastCalledWith("1y", undefined, "", undefined);
141+
expect(getDashboardTrends).toHaveBeenLastCalledWith("1y", undefined, "", undefined);
142+
expect(getDashboardModelDistribution).toHaveBeenLastCalledWith("1y", undefined, "", undefined);
143+
expect(getDashboardRecentEvents).toHaveBeenLastCalledWith("1y", undefined, "", 20, undefined);
144+
});
145+
});
133146
});

frontend/src/features/stats/StatsPage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ export function StatsPage({ language, t, serverMode = false }: StatsPageProps) {
164164
{ label: "24h", value: "24h" },
165165
{ label: "7d", value: "7d" },
166166
{ label: "30d", value: "30d" },
167+
{ label: "1y", value: "1y" },
167168
]}
168169
value={rangeHours}
169170
onChange={(value) => setRangeHours(value as RangeOption)}

frontend/src/lib/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ export async function importSharedAccount(payload: string): Promise<void> {
623623
}
624624
}
625625

626-
export type DashboardRangeKey = "24h" | "7d" | "30d";
626+
export type DashboardRangeKey = "24h" | "7d" | "30d" | "1y";
627627

628628
function dashboardQuery(
629629
range: DashboardRangeKey = "24h",

0 commit comments

Comments
 (0)