SEP-1759: Paginate the periodic-tasks list endpoint and its SEP consumers - #1303
SEP-1759: Paginate the periodic-tasks list endpoint and its SEP consumers#1303peter-o-addo wants to merge 7 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces end-to-end pagination for the Tasks periodic-tasks list (/api/tasks/periodic/) and its SEP gateway proxy (/api/sep/periodic-tasks/), updating both server-rendered pages and React consumers to walk all pages so schedules aren’t truncated at the default page size.
Changes:
- Tasks periodic list now returns a
PaginatedResponseenvelope and uses deterministic ordering to make offset/limit paging stable. - SEP backend consumers forward pagination params upstream and walk all pages where a full set is required (server-rendered views).
- Frontend consumers (ScheduledTasksPanel and API hooks) normalize and walk paginated list responses; OpenAPI specs/clients and tests are updated accordingly.
Reviewed changes
Copilot reviewed 17 out of 19 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| app/tasks/periodic/routes.py | Switch periodic list route to return a PaginatedResponse and accept pagination dependency. |
| app/tasks/periodic/crud.py | Add stable default ordering for PeriodicTask listing to support deterministic paging. |
| app/sep/api/routes/periodic_tasks.py | Forward offset/limit through the SEP proxy and validate/coerce the upstream paginated envelope. |
| app/sep/deps.py | Walk all pages from Tasks periodic list for server-rendered contexts that need the full set. |
| tests/app/tasks/periodic/test_routes.py | Update route tests to assert paginated envelopes and add paging determinism/page-walk coverage. |
| tests/app/tasks/periodic/test_crud.py | Add manager-level ordering/pagination determinism tests. |
| tests/app/sep/api/routes/test_periodic_tasks.py | Update SEP proxy tests to assert paginated envelope forwarding and defaults. |
| frontend/packages/framework/src/components/ScheduledTasksPanel/hooks.ts | Walk all periodic-task pages client-side before filtering schedules to a plugin. |
| frontend/packages/framework/src/components/ScheduledTasksPanel/ScheduledTasksPanel.test.tsx | Add tests for page-walking behavior and array short-circuit behavior. |
| frontend/packages/framework/src/components/SchemaDrivenApp/AppDetailPage.test.tsx | Adjust @sep/api mocks to satisfy new pagination helpers/constants usage. |
| frontend/packages/apps/inventory/src/InventorySchedulePage.test.tsx | Adjust @sep/api mock pattern for compatibility with expanded exports. |
| frontend/packages/api/src/hooks/useAppTasks.ts | Export shared “fetch all pages” helper and document its behavior/caps. |
| frontend/packages/api/src/hooks/index.ts | Re-export pagination helpers/constants from hooks entrypoint. |
| frontend/packages/api/src/index.ts | Re-export pagination helpers/constants from package root. |
| frontend/packages/api/specs/tasks.json | Regenerate Tasks OpenAPI spec for paginated periodic list response + query params. |
| frontend/packages/api/specs/sep.json | Regenerate SEP OpenAPI spec for paginated proxy response + query params. |
| frontend/packages/api/src/generated/tasks.ts | Regenerate TS client types for paginated periodic list endpoint. |
| frontend/packages/api/src/generated/sep.ts | Regenerate TS client types for paginated SEP proxy endpoint. |
| changelog.d/SEP-1759.changed.md | Add changelog entry describing the pagination envelope and page-walking behavior. |
| :cvar ordering: The default ordering for listing periodic tasks. `PeriodicTask` | ||
| is not a `BaseSQLModel`, so `BaseManager._get_ordering()` has no | ||
| `created_at` fallback to offer and would leave SELECTs unordered, making | ||
| offset pagination undefined. The primary key is unique, so ordering by it | ||
| alone is total and needs no tie-breaker. Business-meaningful ordering is | ||
| SEP-304. |
| enabled: bool | None = None, | ||
| ) -> list[PeriodicTask]: | ||
| """List all periodic tasks.""" | ||
| ) -> PaginatedResponse[PeriodicTaskResponse]: |
| /** | ||
| * Walk every page of the SEP periodic-task list before client-side filtering. | ||
| * | ||
| * Mirrors ``fetchAllAppListPages`` so a large schedule table cannot hide this | ||
| * plugin's rows past the first page. Bare-array responses (stories/tests) | ||
| * short-circuit after one request; hitting the page cap surfaces a warning | ||
| * rather than silently dropping schedules. | ||
| */ | ||
| async function fetchAllPeriodicTasks(): Promise<PeriodicTaskResponse[]> { |
1. Paginated envelope + page size
{
"items": [
{
"name": "run_test-mydumpers_every_5_days_-1940910910313412493",
"task": "test-mydumpers",
"start_time": null,
"enabled": true,
"description": "",
"execute_request": {
"meta": {},
"payload": null,
"eta": null,
"anonymize_mask": null,
"chain_task_names": [
"test-mydumper-secondary"
],
"chain_on_failure": false
},
"interval": {
"every": 5,
"period": "days"
},
"crontab": null,
"id": 17,
"last_run_at": null,
"last_run_status": null,
"total_run_count": 0,
"date_changed": "2026-05-20T11:45:00.162865Z",
"period": "every 5 days",
"next_run_at": "2026-08-12T10:48:50.222178Z"
},
{
"name": "run_test-mydumper_every_1_hour_-326798780279067240",
"task": "test-mydumper",
"start_time": null,
"enabled": true,
"description": "",
"execute_request": null,
"interval": {
"every": 1,
"period": "hours"
},
"crontab": null,
"id": 25,
"last_run_at": null,
"last_run_status": null,
"total_run_count": 0,
"date_changed": "2026-07-21T15:08:57.552856Z",
"period": "every 1 hour",
"next_run_at": "2026-08-07T11:48:50.222187Z"
}
],
"total": 2,
"offset": 0,
"limit": 2
}2. Stable window (same query twice)
Call 1 {
"items": [
{
"id": 17,
"name": "run_test-mydumpers_every_5_days_-1940910910313412493",
"task": "test-mydumpers",
"enabled": true,
"last_run_status": null
},
{
"id": 25,
"name": "run_test-mydumper_every_1_hour_-326798780279067240",
"task": "test-mydumper",
"enabled": true,
"last_run_status": null
}
],
"total": 2,
"offset": 0,
"limit": 2
}Call 2 {
"items": [
{
"id": 17,
"name": "run_test-mydumpers_every_5_days_-1940910910313412493",
"task": "test-mydumpers",
"enabled": true,
"last_run_status": null
},
{
"id": 25,
"name": "run_test-mydumper_every_1_hour_-326798780279067240",
"task": "test-mydumper",
"enabled": true,
"last_run_status": null
}
],
"total": 2,
"offset": 0,
"limit": 2
}3. Multi-page walk (seeded to
|
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
Summary
Paginate the periodic-tasks list end-to-end so unbounded schedule responses become a PaginatedResponse envelope and every SEP consumer still sees the full set.
app/tasks/periodic/crud.py,app/tasks/periodic/routes.py: add stable list ordering and return a paginated/periodic/page on both filter branchesapp/sep/api/routes/periodic_tasks.py,app/sep/deps.py: forward offset/limit through the SEP proxy and walk all pages for server-rendered task viewsfrontend/packages/framework/.../ScheduledTasksPanel/hooks.ts,frontend/packages/api/src/hooks/useAppTasks.ts(+ package exports): walk all periodic-task pages before client-side plugin filtering, and export the shared page-cap helpersfrontend/packages/api/specs/{tasks,sep}.json,frontend/packages/api/src/generated/{tasks,sep}.ts,changelog.d/SEP-1759.changed.md: regenerate OpenAPI clients for the new envelope and record the changelogTested
GET /api/tasks/periodic/?offset=0&limit=2returns aPaginatedResponseenvelope (items,total,offset,limit) andlen(items) <= limitoffset/limitquery twice returns a stable id window ([17, 25])total=5, walk withlimit=2— unique ids, sorted ascending,walked == totalGET /api/tasks/periodic/?enabled=true&offset=0&limit=2returns only enabled rows in the paginated envelopeowner=ALTERS→ empty envelope;owner=BACKUPS(after seed) → filtered page (total=4, ids[25, 35])Checklist
make test)make run-pre-commit)make makemigrations)changelog.d/if the change is user-facing (make changelog-add), or confirmed N/A (internal-only change, or a same-release-cycle fix for an unreleased sibling ticket)