Skip to content

Commit 9f55e3a

Browse files
committed
Add home dashboard usage insights
1 parent f4e3117 commit 9f55e3a

5 files changed

Lines changed: 842 additions & 178 deletions

File tree

726 KB
Loading

src/features/app/hooks/useMainAppLayoutSurfaces.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,10 @@ export function useMainAppLayoutSurfaces({
603603
usageWorkspaceId,
604604
usageWorkspaceOptions,
605605
onUsageWorkspaceChange,
606+
accountRateLimits: activeRateLimits,
607+
usageShowRemaining: appSettings.usageShowRemaining,
608+
accountInfo: activeAccount,
609+
accountWorkspaceLabel: activeWorkspace?.name ?? null,
606610
onSelectThread: (workspaceId, threadId) => {
607611
threadNavigation.exitDiffView();
608612
threadNavigation.clearDraftState();

src/features/home/components/Home.test.tsx

Lines changed: 234 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
// @vitest-environment jsdom
2-
import { fireEvent, render, screen } from "@testing-library/react";
3-
import { describe, expect, it, vi } from "vitest";
2+
import { cleanup, fireEvent, render, screen, within } from "@testing-library/react";
3+
import { afterEach, describe, expect, it, vi } from "vitest";
44
import { Home } from "./Home";
55

6+
afterEach(() => {
7+
cleanup();
8+
});
9+
610
const baseProps = {
711
onOpenSettings: vi.fn(),
812
onAddWorkspace: vi.fn(),
@@ -18,6 +22,10 @@ const baseProps = {
1822
usageWorkspaceId: null,
1923
usageWorkspaceOptions: [],
2024
onUsageWorkspaceChange: vi.fn(),
25+
accountRateLimits: null,
26+
usageShowRemaining: false,
27+
accountInfo: null,
28+
accountWorkspaceLabel: null,
2129
onSelectThread: vi.fn(),
2230
};
2331

@@ -99,5 +107,229 @@ describe("Home", () => {
99107
expect(screen.getAllByText("agent time").length).toBeGreaterThan(0);
100108
expect(screen.getByText("Runs")).toBeTruthy();
101109
expect(screen.getByText("Peak day")).toBeTruthy();
110+
expect(screen.getByText("Avg / run")).toBeTruthy();
111+
expect(screen.getByText("Avg / active day")).toBeTruthy();
112+
expect(screen.getByText("Longest streak")).toBeTruthy();
113+
expect(screen.getByText("Active days")).toBeTruthy();
114+
});
115+
116+
it("renders expanded token stats and account limits", () => {
117+
render(
118+
<Home
119+
{...baseProps}
120+
localUsageSnapshot={{
121+
updatedAt: Date.now(),
122+
days: [
123+
{
124+
day: "2026-01-07",
125+
inputTokens: 20,
126+
cachedInputTokens: 5,
127+
outputTokens: 10,
128+
totalTokens: 30,
129+
agentTimeMs: 60000,
130+
agentRuns: 1,
131+
},
132+
{
133+
day: "2026-01-08",
134+
inputTokens: 10,
135+
cachedInputTokens: 0,
136+
outputTokens: 5,
137+
totalTokens: 15,
138+
agentTimeMs: 0,
139+
agentRuns: 0,
140+
},
141+
{
142+
day: "2026-01-09",
143+
inputTokens: 0,
144+
cachedInputTokens: 0,
145+
outputTokens: 0,
146+
totalTokens: 0,
147+
agentTimeMs: 0,
148+
agentRuns: 0,
149+
},
150+
{
151+
day: "2026-01-10",
152+
inputTokens: 0,
153+
cachedInputTokens: 0,
154+
outputTokens: 0,
155+
totalTokens: 0,
156+
agentTimeMs: 0,
157+
agentRuns: 0,
158+
},
159+
{
160+
day: "2026-01-11",
161+
inputTokens: 0,
162+
cachedInputTokens: 0,
163+
outputTokens: 0,
164+
totalTokens: 0,
165+
agentTimeMs: 0,
166+
agentRuns: 0,
167+
},
168+
{
169+
day: "2026-01-12",
170+
inputTokens: 0,
171+
cachedInputTokens: 0,
172+
outputTokens: 0,
173+
totalTokens: 0,
174+
agentTimeMs: 0,
175+
agentRuns: 0,
176+
},
177+
{
178+
day: "2026-01-13",
179+
inputTokens: 30,
180+
cachedInputTokens: 10,
181+
outputTokens: 20,
182+
totalTokens: 50,
183+
agentTimeMs: 120000,
184+
agentRuns: 2,
185+
},
186+
{
187+
day: "2026-01-14",
188+
inputTokens: 35,
189+
cachedInputTokens: 10,
190+
outputTokens: 15,
191+
totalTokens: 50,
192+
agentTimeMs: 120000,
193+
agentRuns: 2,
194+
},
195+
{
196+
day: "2026-01-15",
197+
inputTokens: 25,
198+
cachedInputTokens: 5,
199+
outputTokens: 15,
200+
totalTokens: 40,
201+
agentTimeMs: 120000,
202+
agentRuns: 2,
203+
},
204+
{
205+
day: "2026-01-16",
206+
inputTokens: 15,
207+
cachedInputTokens: 5,
208+
outputTokens: 10,
209+
totalTokens: 25,
210+
agentTimeMs: 60000,
211+
agentRuns: 1,
212+
},
213+
{
214+
day: "2026-01-17",
215+
inputTokens: 0,
216+
cachedInputTokens: 0,
217+
outputTokens: 0,
218+
totalTokens: 0,
219+
agentTimeMs: 0,
220+
agentRuns: 0,
221+
},
222+
{
223+
day: "2026-01-18",
224+
inputTokens: 20,
225+
cachedInputTokens: 8,
226+
outputTokens: 12,
227+
totalTokens: 32,
228+
agentTimeMs: 90000,
229+
agentRuns: 1,
230+
},
231+
{
232+
day: "2026-01-19",
233+
inputTokens: 40,
234+
cachedInputTokens: 10,
235+
outputTokens: 25,
236+
totalTokens: 65,
237+
agentTimeMs: 180000,
238+
agentRuns: 3,
239+
},
240+
{
241+
day: "2026-01-20",
242+
inputTokens: 20,
243+
cachedInputTokens: 4,
244+
outputTokens: 16,
245+
totalTokens: 36,
246+
agentTimeMs: 120000,
247+
agentRuns: 2,
248+
},
249+
],
250+
totals: {
251+
last7DaysTokens: 248,
252+
last30DaysTokens: 343,
253+
averageDailyTokens: 35,
254+
cacheHitRatePercent: 25,
255+
peakDay: "2026-01-19",
256+
peakDayTokens: 65,
257+
},
258+
topModels: [{ model: "gpt-5", tokens: 300, sharePercent: 87.5 }],
259+
}}
260+
accountRateLimits={{
261+
primary: {
262+
usedPercent: 62,
263+
windowDurationMins: 300,
264+
resetsAt: Math.round(Date.now() / 1000) + 3600,
265+
},
266+
secondary: {
267+
usedPercent: 34,
268+
windowDurationMins: 10080,
269+
resetsAt: Math.round(Date.now() / 1000) + 86400,
270+
},
271+
credits: {
272+
hasCredits: true,
273+
unlimited: true,
274+
balance: null,
275+
},
276+
planType: "pro",
277+
}}
278+
accountInfo={{
279+
type: "chatgpt",
280+
email: "user@example.com",
281+
planType: "pro",
282+
requiresOpenaiAuth: false,
283+
}}
284+
accountWorkspaceLabel="CodexMonitor"
285+
/>,
286+
);
287+
288+
expect(screen.getByText("Cached tokens")).toBeTruthy();
289+
expect(screen.getByText("Avg / run")).toBeTruthy();
290+
expect(screen.getByText("Longest streak")).toBeTruthy();
291+
expect(screen.getByText("4 days")).toBeTruthy();
292+
expect(screen.getByText("Account limits")).toBeTruthy();
293+
expect(screen.getByText("Unlimited")).toBeTruthy();
294+
expect(screen.getByText("Pro")).toBeTruthy();
295+
expect(screen.getByText(/user@example\.com/)).toBeTruthy();
296+
297+
const todayCard = screen.getByText("Today").closest(".home-usage-card");
298+
expect(todayCard).toBeTruthy();
299+
if (!(todayCard instanceof HTMLElement)) {
300+
throw new Error("Expected today usage card");
301+
}
302+
expect(within(todayCard).getByText("36")).toBeTruthy();
303+
304+
expect(
305+
screen.getByLabelText("Usage week 2026-01-14 to 2026-01-20"),
306+
).toBeTruthy();
307+
expect(
308+
(screen.getByRole("button", { name: "Show next week" }) as HTMLButtonElement)
309+
.disabled,
310+
).toBe(true);
311+
expect(
312+
screen.getByText("Jan 20").closest(".home-usage-bar")?.getAttribute("data-value"),
313+
).toBe("Jan 20 · 36 tokens");
314+
315+
fireEvent.click(screen.getByRole("button", { name: "Show previous week" }));
316+
317+
expect(
318+
screen.getByLabelText("Usage week 2026-01-07 to 2026-01-13"),
319+
).toBeTruthy();
320+
expect(
321+
(screen.getByRole("button", { name: "Show next week" }) as HTMLButtonElement)
322+
.disabled,
323+
).toBe(false);
324+
325+
fireEvent.click(screen.getByRole("button", { name: "Show next week" }));
326+
327+
expect(
328+
screen.getByLabelText("Usage week 2026-01-14 to 2026-01-20"),
329+
).toBeTruthy();
330+
expect(
331+
(screen.getByRole("button", { name: "Show next week" }) as HTMLButtonElement)
332+
.disabled,
333+
).toBe(true);
102334
});
103335
});

0 commit comments

Comments
 (0)