Skip to content

Paginate the asset library with infinite scroll (Closes #970)#973

Merged
cjimti merged 2 commits into
mainfrom
fix/970-asset-pagination
Jul 16, 2026
Merged

Paginate the asset library with infinite scroll (Closes #970)#973
cjimti merged 2 commits into
mainfrom
fix/970-asset-pagination

Conversation

@cjimti

@cjimti cjimti commented Jul 16, 2026

Copy link
Copy Markdown
Member

Summary

The portal asset library now loads every asset via infinite scroll instead of stopping at the first page, resolving #970. A small set of local make dev improvements is bundled in so the change is demonstrable out of the box: make dev no longer collides with other running stacks, and the dev seed carries enough assets to actually exercise pagination.

Closes #970.

The asset pagination fix

Before, the My Assets and Admin Assets pages fetched only the first page (server default of 50) and rendered a static "Showing X of Y" footer with no way to reach the rest, so a user with more than 50 assets could never see them.

Now both pages accumulate pages and load everything:

  • As the list scrolls near the bottom, the next page loads automatically. This works identically in grid (thumbnail) and table views.
  • A "Load more" button remains as a manual fallback and as the loading indicator.
  • The "Showing X of Y assets" footer stays visible while more remain and hides once everything is loaded.

Implementation:

  • New useInfiniteQuery-backed hooks (useInfiniteAssets, useInfiniteSharedWithMe, useInfiniteAdminAssets) accumulate pages and de-duplicate rows by id, so offset paging over a created_at DESC list cannot re-emit a row or overcount when the window shifts. Page total is taken from the latest page so items added after the first fetch stay reachable, and an empty trailing page ends pagination so the control cannot spin forever.
  • A shared useInfiniteScroll hook (ui/src/hooks/useInfiniteScroll.ts) drives the auto-load for both pages. It uses a scroll listener with a near-bottom threshold rather than an IntersectionObserver, which did not fire reliably for content inside the portal's nested overflow-auto container. It attaches to the nearest scroll parent, reads the latest callback through a ref so scope and filter changes are picked up without re-attaching, and no-ops on degenerate layouts (zero scroll height, e.g. jsdom) so the "Load more" button remains the fallback.

No backend change is required: the /portal/assets, /portal/shared-with-me, and admin /assets endpoints already support limit and offset and return a real total.

Local dev improvements (bundled so the fix is demonstrable)

make dev port auto-relocation:

  • When the default host ports (5432 postgres, 8080 API, 9000 S3, 11434 Ollama) are already held by another stack (for example a running DataHub quickstart or a leftover e2e stack), make dev now shifts those four together to the first free port window and threads the resolved values through docker-compose, platform.yaml (via ${DEV_*_PORT} expansion), seed-s3.sh, Vite (VITE_API_TARGET), and every startup curl. No flags and no host setup are required; with no conflict the ports are unchanged.
  • Keycloak, Vite, Prometheus, the metrics endpoint, and the upstream fixtures deliberately keep their fixed ports, because those are baked into the Keycloak realm's OIDC redirect URIs and the Prometheus scrape target. The API-port move stays invisible to OIDC because the browser always reaches the server through Vite's proxy on :5173.
  • The resolved ports are written to a gitignored dev/.dev-ports.env so make dev-info reprints the correct URLs.

Dev seed volume:

  • The dev seed now creates 120 generated demo assets (126 total) with content uploaded per type, so the portal library has enough volume to exercise pagination the moment make dev comes up. Six assets never surfaced the 50-per-page behavior.

Testing

  • Frontend suite green: 308 tests pass, eslint reports 0 errors, tsc --noEmit clean, production build succeeds.
  • New unit tests cover the page merge and id de-duplication (assets.test.ts), the useInfiniteScroll hook across its near-bottom, no-more, in-flight, and degenerate-layout cases (useInfiniteScroll.test.ts), and the page-level pagination and empty-state behavior (MyAssetsPage.test.tsx).
  • The dev scripts are shellcheck clean and seed.sql was validated against a migrated schema (126 assets, even spread across the five content types).
  • Verified end to end in a live make dev: with 126 seeded assets, scrolling auto-loads 50 to 126 in both grid and table views, and the footer and "Load more" control retire once everything is loaded.

Scope and follow-up

This PR paginates the asset library only. A survey of the other portal list surfaces (Resources, Collections, Feedback, Knowledge Pages, Users, Changelog, and the DataHub-backed browse tabs) found the same first-page cap on several of them; that work is tracked separately in #972 and reuses the useInfiniteScroll hook introduced here.

cjimti added 2 commits July 15, 2026 18:57
…970)

The My Assets and Admin Assets pages fetched only the first page (server default 50) and rendered a static "Showing X of Y" footer with no way to load the rest, so a user with more than 50 assets could never see them.

Both pages now use useInfiniteQuery-backed hooks (useInfiniteAssets / useInfiniteSharedWithMe / useInfiniteAdminAssets) that accumulate pages with id-deduplication, plus a shared useInfiniteScroll hook that auto-loads the next page as the list scrolls near the bottom (issue asked for "more assets load as I scroll down"). A "Load more" button remains as a manual fallback and loading indicator, and the behaviour is identical in grid (thumbnail) and table views. IntersectionObserver proved unreliable inside the portal's nested overflow-auto container, so the hook uses a scroll listener with a near-bottom threshold and degrades to the button where there is no scroll container (jsdom/tests).
make dev now detects when the default host ports (5432/8080/9000/11434) are held by another stack (e.g. a running DataHub or leftover e2e postgres) and shifts postgres/api/s3/ollama together to the first free port window, threading the resolved DEV_*_PORT values through docker-compose, platform.yaml, seed-s3.sh, Vite (VITE_API_TARGET) and every curl. Keycloak, Vite, Prometheus, the metrics endpoint and the upstream fixtures stay on their fixed ports so the Keycloak realm's OIDC redirect URIs and the Prometheus scrape target keep working; the API-port move stays invisible to OIDC because the browser reaches the server through Vite's :5173 proxy. Resolved ports are persisted to gitignored dev/.dev-ports.env so make dev-info reprints correct URLs.

The dev seed also creates 120 generated demo assets (126 total, content uploaded per type by seed-s3.sh) so the portal library exercises pagination out of the box.
@cjimti
cjimti merged commit 01d36b1 into main Jul 16, 2026
8 checks passed
@cjimti
cjimti deleted the fix/970-asset-pagination branch July 16, 2026 02:15
@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.04%. Comparing base (dcdc9ea) to head (d49f5d9).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #973   +/-   ##
=======================================
  Coverage   89.03%   89.04%           
=======================================
  Files         424      424           
  Lines       48581    48581           
=======================================
+ Hits        43255    43257    +2     
+ Misses       3550     3549    -1     
+ Partials     1776     1775    -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Can only view 50 assets

1 participant