feat: merge Limits + Usage into unified Spend page (#24093)

This commit is contained in:
Danielle Maywood
2026-04-09 13:17:03 +01:00
committed by GitHub
parent 27e5ff0a8e
commit 29ad2c6201
29 changed files with 2351 additions and 1625 deletions
+29 -27
View File
@@ -7,8 +7,6 @@ import {
cancelChatListRefetches,
chatCostSummary,
chatCostSummaryKey,
chatCostUsers,
chatCostUsersKey,
chatDiffContentsKey,
chatKey,
chatMessagesKey,
@@ -20,6 +18,7 @@ import {
infiniteChats,
interruptChat,
invalidateChatListQueries,
paginatedChatCostUsers,
pinChat,
promoteChatQueuedMessage,
regenerateChatTitle,
@@ -176,20 +175,6 @@ describe("invalidateChatListQueries", () => {
).toBe(true);
});
it("does not invalidate chatCostUsersKey", async () => {
const queryClient = createTestQueryClient();
queryClient.setQueryData(chatCostUsersKey(undefined), {});
queryClient.setQueryData(chatsKey, [makeChat("chat-1")]);
await invalidateChatListQueries(queryClient);
expect(
queryClient.getQueryState(chatCostUsersKey(undefined))?.isInvalidated,
"chatCostUsersKey should NOT be invalidated",
).not.toBe(true);
});
it("does not invalidate a different chat's queries", async () => {
const queryClient = createTestQueryClient();
const chatId = "chat-1";
@@ -631,25 +616,42 @@ describe("chat cost query factories", () => {
);
});
it("builds a distinct users query key and forwards snake_case params", async () => {
const params = {
it("builds paginated cost users query with correct key and coerces empty username", async () => {
const payload = {
start_date: "2025-01-01",
end_date: "2025-01-31",
username: "alice",
limit: 10,
offset: 20,
username: "",
};
vi.mocked(API.experimental.getChatCostUsers).mockResolvedValue(
{} as TypesGen.ChatCostUsersResponse,
);
const result = paginatedChatCostUsers(payload);
const query = chatCostUsers(params);
// queryPayload returns the original payload.
const pageParams = {
pageNumber: 2,
limit: 25,
offset: 25,
searchParams: new URLSearchParams(),
};
expect(result.queryPayload(pageParams)).toEqual(payload);
expect(chatCostUsersKey(params)).toEqual(["chats", "costUsers", params]);
expect(query.queryKey).toEqual(["chats", "costUsers", params]);
expect(query.queryKey).not.toEqual(chatCostSummaryKey("me", params));
await query.queryFn();
expect(API.experimental.getChatCostUsers).toHaveBeenCalledWith(params);
// queryKey includes the payload and page number.
const key = result.queryKey({ ...pageParams, payload });
expect(key).toEqual(["chats", "costUsers", payload, 2]);
// queryFn coerces empty username to undefined.
// Cast needed because PaginatedQueryFnContext includes
// react-query internal fields that aren't relevant here.
await (
result.queryFn as (params: Record<string, unknown>) => Promise<unknown>
)({
...pageParams,
payload,
});
expect(API.experimental.getChatCostUsers).toHaveBeenCalledWith(
expect.objectContaining({ username: undefined, limit: 25, offset: 25 }),
);
});
});
+27 -13
View File
@@ -5,6 +5,7 @@ import type {
} from "react-query";
import { API } from "#/api/api";
import type * as TypesGen from "#/api/typesGenerated";
import type { UsePaginatedQueryOptions } from "#/hooks/usePaginatedQuery";
export const chatsKey = ["chats"] as const;
export const chatKey = (chatId: string) => ["chats", chatId] as const;
@@ -992,12 +993,6 @@ type ChatCostDateParams = {
end_date?: string;
};
type ChatCostUsersParams = ChatCostDateParams & {
username?: string;
limit?: number;
offset?: number;
};
export const chatCostSummaryKey = (user = "me", params?: ChatCostDateParams) =>
[...chatsKey, "costSummary", user, params] as const;
@@ -1007,14 +1002,33 @@ export const chatCostSummary = (user = "me", params?: ChatCostDateParams) => ({
staleTime: 60_000,
});
export const chatCostUsersKey = (params?: ChatCostUsersParams) =>
[...chatsKey, "costUsers", params] as const;
interface PaginatedChatCostUsersPayload {
username: string;
start_date: string;
end_date: string;
}
export const chatCostUsers = (params?: ChatCostUsersParams) => ({
queryKey: chatCostUsersKey(params),
queryFn: () => API.experimental.getChatCostUsers(params),
staleTime: 60_000,
});
export function paginatedChatCostUsers(
payload: PaginatedChatCostUsersPayload,
): UsePaginatedQueryOptions<
TypesGen.ChatCostUsersResponse,
PaginatedChatCostUsersPayload
> {
return {
queryPayload: () => payload,
queryKey: ({ payload, pageNumber }) =>
[...chatsKey, "costUsers", payload, pageNumber] as const,
queryFn: ({ payload, limit, offset }) =>
API.experimental.getChatCostUsers({
start_date: payload.start_date,
end_date: payload.end_date,
username: payload.username || undefined,
limit,
offset,
}),
staleTime: 60_000,
};
}
const prInsightsKey = (params?: { start_date?: string; end_date?: string }) =>
[...chatsKey, "prInsights", params] as const;