fix(ui): block usage export while the first activity page is loading
Some checks failed
Terraform Modules / fmt, validate, test (aws) (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled

Also drop the explanatory comments the repo guidelines disallow.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Devin AI 2026-08-18 01:13:54 +00:00
parent fe131b807a
commit 45ad441c48
4 changed files with 19 additions and 12 deletions

View file

@ -74,14 +74,6 @@ const mergeDay = (a: DailyData, b: DailyData): DailyData => ({
breakdown: mergeBreakdown(a.breakdown, b.breakdown),
});
/**
* Combine daily activity pages into one series with a single entry per date.
*
* The backend paginates over raw spend rows, so a date whose rows straddle a
* page boundary comes back once per page, each entry holding only that page's
* share of the day. Concatenating those entries leaves duplicate dates that
* under-report every per-day figure in the charts and the CSV export.
*/
export const mergeDailyResults = (existing: readonly DailyData[], incoming: readonly DailyData[]): DailyData[] =>
incoming.reduce<DailyData[]>(
(acc, day) => {

View file

@ -119,6 +119,24 @@ describe("usePaginatedDailyActivity", () => {
expect(result.current.incomplete).toBe(false);
});
it("stays incomplete while the first page is still in flight", async () => {
let resolveFirstPage: (value: ReturnType<typeof page>) => void = () => {};
const fetchFn = vi.fn().mockReturnValueOnce(
new Promise<ReturnType<typeof page>>((resolve) => {
resolveFirstPage = resolve;
}),
);
const { result } = renderHook(() => usePaginatedDailyActivity({ fetchFn, args, enabled: true }));
await waitFor(() => expect(result.current.loading).toBe(true), { timeout: 3000 });
expect(result.current.incomplete).toBe(true);
resolveFirstPage(page("2026-06-25", 22.38, 1, 1));
await waitFor(() => expect(result.current.incomplete).toBe(false), { timeout: 3000 });
});
it("flags the range as incomplete when a page fetch fails instead of looking complete", async () => {
const fetchFn = vi
.fn()

View file

@ -49,9 +49,7 @@ interface UsePaginatedDailyActivityReturn {
isFetchingMore: boolean;
progress: PaginationProgress;
cancelled: boolean;
/** True when a page fetch failed, so the data on screen covers only part of the range. */
failed: boolean;
/** True whenever the data on screen is known not to cover the whole requested range. */
incomplete: boolean;
cancel: () => void;
}
@ -250,7 +248,7 @@ export function usePaginatedDailyActivity({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [enabled, fetchFn, argsKey]);
const incomplete = isFetchingMore || cancelled || failed;
const incomplete = loading || isFetchingMore || cancelled || failed;
return { data, loading, isFetchingMore, progress, cancelled, failed, incomplete, cancel };
}

View file

@ -34,7 +34,6 @@ interface UsageExportHeaderProps {
customTitle?: string;
compactLayout?: boolean;
teams?: Team[];
/** Blocks the export while the data on screen does not cover the whole requested range. */
exportDisabled?: boolean;
exportDisabledReason?: string;
}