feat(jellyseer): sortable/filterable requests table on the Requests tab
Replace the static "recent requests" list with a proper table of all Jellyseerr requests, sorted by date added (newest first by default) with standard sorting and filtering. Backend: - JellyseerrClient.requests(max_count=500): paginated GET /api/v1/request (sort=added), mapped with type (movie/tv), status, media_status, and created_at labels. Returns up to 500 so the table can sort/filter client-side. - fetch_jellyseer_requests(service) reuses the per-service cached client (shared with the stats widgets). - new GET /api/jellyseerr/requests endpoint. Frontend: - JellyseerRequestsTable: TanStack Table (sorting via getSortedRowModel, pagination via getPaginationRowModel) reusing the Table primitives + TablePagination. Columns: Name / Type / Status / Media / Requested, all sortable; default sort Requested desc. A search box filters by name and a status dropdown defaults to "Open" (pending+approved+processing) with All/Pending/Approved/Declined options. (The shared DataTable is deliberately visibility-only, so this is a dedicated sortable table.) - RequestsTab renders the stats grid + the new table (the compact recent list stays on the Requests overview widget). - useJellyseerRequests hook + fetchJellyseerRequests API client. Tests: client requests() mapping + single-page stop; fetch helper not-configured; RequestsTab test mocks both hooks. 404/404 backend + 184/184 frontend pass; build (tsc -b && vite build) + ESLint clean.
This commit is contained in:
@@ -115,3 +115,49 @@ def test_stat_widget_unknown_stat_returns_error():
|
||||
with patch("media_library_viewer_api.widgets.sources.get_stats_provider", return_value=provider):
|
||||
data = asyncio.run(src.fetch(_service(), "stat", {"stat": "nope"}))
|
||||
assert "error" in data
|
||||
|
||||
|
||||
def test_jellyseer_client_requests_maps_fields():
|
||||
"""requests() paginates /request and maps type/status/media_status enums."""
|
||||
from media_library_viewer_api.clients.jellyseerr import JellyseerrClient
|
||||
|
||||
c = JellyseerrClient("https://js.example.com", "key")
|
||||
c.session = MagicMock()
|
||||
resp = MagicMock()
|
||||
resp.raise_for_status.return_value = None
|
||||
resp.status_code = 200
|
||||
resp.text = ""
|
||||
resp.json.return_value = {
|
||||
"results": [
|
||||
{
|
||||
"id": 7,
|
||||
"type": 1,
|
||||
"title": "Inception",
|
||||
"status": 1,
|
||||
"media": {"status": 5},
|
||||
"createdAt": 1_700_000_000,
|
||||
}
|
||||
]
|
||||
}
|
||||
c.session.get.return_value = resp
|
||||
|
||||
out = c.requests(500)
|
||||
|
||||
assert len(out) == 1
|
||||
r = out[0]
|
||||
assert r["id"] == 7
|
||||
assert r["type"] == "movie" # 1 -> movie
|
||||
assert r["name"] == "Inception"
|
||||
assert r["status"] == "pending" # 1 -> pending
|
||||
assert r["media_status"] == "available" # 5 -> available
|
||||
assert r["created_at"] == 1_700_000_000
|
||||
# Single short page -> no second fetch.
|
||||
assert c.session.get.call_count == 1
|
||||
|
||||
|
||||
def test_fetch_jellyseer_requests_not_configured_returns_empty():
|
||||
"""No jellyseerr config -> empty list (tab shows 'No requests')."""
|
||||
from media_library_viewer_api.widgets.jellyseerr_stats import fetch_jellyseer_requests
|
||||
|
||||
service = ServiceRecord(id="s", service_type="jellyfin", name="JF", config={}, secrets={})
|
||||
assert fetch_jellyseer_requests(service) == []
|
||||
|
||||
Reference in New Issue
Block a user