Task #48: Let admins page through more than the last 100 opens
Added offset/limit pagination to the two admin app-opens drill-in endpoints so admins can investigate spikes that span more than the default 100 most-recent opens. Backend (artifacts/api-server/src/routes/stats.ts): - New parsePaging() helper validates `limit` (1..200, default 100) and `offset` (>=0, default 0); invalid values return 400. - Both `/stats/admin/app-opens/by-app/:appId` and `/stats/admin/app-opens/by-user/:userId` accept the new params, apply `.limit(limit).offset(offset)`, and return `limit`, `offset`, and a `nextOffset` (number | null) computed from `totalCount`. - Added a stable secondary sort (`id desc`) so paged results don't shuffle when timestamps tie. Spec & client (lib/api-spec/openapi.yaml + regenerated clients): - Added `limit`/`offset` query params and `limit`/`offset`/`nextOffset` response fields to AdminAppOpensByApp/AdminAppOpensByUser. - Re-ran `pnpm --filter @workspace/api-spec run codegen`. Frontend (artifacts/teaboy-os/src/pages/admin.tsx + locales): - AppOpensDrillIn / UserOpensDrillIn now accumulate extra pages in local state and expose a "Load more" button via a shared LoadMoreSection footer that also shows "Showing X of Y". - Extra pages are fetched via the generated `getAdminAppOpensByApp` / `getAdminAppOpensByUser` functions; accumulated state resets when the appId/userId or stats query params change. - Added en/ar translations for `loadMore`, `loadMoreError`, `shownOf`. Tests: - New artifacts/api-server/tests/admin-app-opens-pagination.test.mjs covers happy-path paging for both endpoints, the default page size, and 400 responses for invalid limit/offset. - All 10 api-server tests pass; full workspace typecheck passes.
This commit is contained in:
@@ -521,6 +521,10 @@ export interface AdminAppOpensByApp {
|
||||
rangeFrom: string;
|
||||
rangeTo: string;
|
||||
totalCount: number;
|
||||
limit: number;
|
||||
offset: number;
|
||||
/** @nullable */
|
||||
nextOffset: number | null;
|
||||
opens: AppOpenByAppEntry[];
|
||||
}
|
||||
|
||||
@@ -559,6 +563,10 @@ export interface AdminAppOpensByUser {
|
||||
rangeFrom: string;
|
||||
rangeTo: string;
|
||||
totalCount: number;
|
||||
limit: number;
|
||||
offset: number;
|
||||
/** @nullable */
|
||||
nextOffset: number | null;
|
||||
opens: AppOpenByUserEntry[];
|
||||
}
|
||||
|
||||
@@ -599,6 +607,15 @@ export type GetAdminAppOpensByAppParams = {
|
||||
range?: GetAdminAppOpensByAppRange;
|
||||
from?: string;
|
||||
to?: string;
|
||||
/**
|
||||
* @minimum 1
|
||||
* @maximum 200
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* @minimum 0
|
||||
*/
|
||||
offset?: number;
|
||||
};
|
||||
|
||||
export type GetAdminAppOpensByAppRange =
|
||||
@@ -615,6 +632,15 @@ export type GetAdminAppOpensByUserParams = {
|
||||
range?: GetAdminAppOpensByUserRange;
|
||||
from?: string;
|
||||
to?: string;
|
||||
/**
|
||||
* @minimum 1
|
||||
* @maximum 200
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* @minimum 0
|
||||
*/
|
||||
offset?: number;
|
||||
};
|
||||
|
||||
export type GetAdminAppOpensByUserRange =
|
||||
|
||||
Reference in New Issue
Block a user