Skip to content

FLPATH-4285 | [DCM] Server-side pagination not implemented - #4155

Open
asmasarw wants to merge 4 commits into
redhat-developer:mainfrom
asmasarw:feature/new-api
Open

FLPATH-4285 | [DCM] Server-side pagination not implemented#4155
asmasarw wants to merge 4 commits into
redhat-developer:mainfrom
asmasarw:feature/new-api

Conversation

@asmasarw

@asmasarw asmasarw commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Add server-side cursor pagination to all DCM tabs.

Previously only Service Types, Catalog Items, and Instances fetched data page-by-page. Providers, Policies, and Resources loaded everything in a single call, silently dropping records beyond the first page.

What changed

  • Providers & Policies — converted to cursor pagination with Next/Previous controls; search resets to page 1 client-side.
  • Resources — converted to usePaginatedFetch (read-only layout preserved).
  • dcm-common — extracted buildPaginationQuery utility; listProviders and listPolicies now accept optional PaginationParams; next_page_token made optional on list response types to match real backend behaviour.
  • Dropdown calls capped at 25 items — service-type and catalog-item selects now pass max_page_size: 25.
  • Tests — added ProvidersTabContent.test.tsx and ResourcesTabContent.test.tsx; extended PoliciesTabContent.test.tsx with cursor navigation tests.
  • Translations — added common.previousPage / common.nextPage to all locale files.

@asmasarw
asmasarw requested review from a team, jkilzi and mareklibra as code owners August 4, 2026 07:51
@rhdh-gh-app

rhdh-gh-app Bot commented Aug 4, 2026

Copy link
Copy Markdown

Important

This PR includes changes that affect public-facing API. Please ensure you are adding/updating documentation for new features or behavior.

Changed Packages

Package Name Package Path Changeset Bump Current Version
@red-hat-developer-hub/backstage-plugin-dcm-common workspaces/dcm/plugins/dcm-common minor v1.0.0
@red-hat-developer-hub/backstage-plugin-dcm workspaces/dcm/plugins/dcm minor v1.0.0

@rhdh-qodo-merge

Copy link
Copy Markdown

PR Summary by Qodo

Add server-side cursor pagination across all DCM tabs

✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Enable server-side cursor pagination for all six DCM tabs.
• Standardize pagination params/query building across dcm-common API clients.
• Add cursor navigation test coverage (Next/Previous, retry, token passing).
Diagram

graph TD
  UI["DCM tab pages"] --> H1["useCrudTab (cursor mode)"] --> API1["dcm-common API clients"] --> BE["DCM backend list APIs"]
  UI --> H2["usePaginatedFetch"] --> API1
  API1 --> U1["buildPaginationQuery"]
  UI --> C1["CursorPaginationControls"]
  UI --> L1["DcmCrudTabLayout / DcmSearchTableCard"] --> C1
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Centralize cursor token-stack in useCrudTab
  • ➕ Eliminates duplicated cursor state/handlers across Providers/Policies/Catalog tabs
  • ➕ Reduces risk of subtle divergence (e.g., search reset semantics, disabled states)
  • ➖ Bigger behavioral surface area change in a core hook used by multiple tabs
  • ➖ Harder to keep hook API simple (needs pageSize, resetCursor, maybe total-count semantics)
2. Use usePaginatedFetch for all tabs (including CRUD tabs)
  • ➕ One pagination implementation and test suite for all list views
  • ➕ Simplifies tab components to mapping API response → {items,nextPageToken}
  • ➖ Requires additional plumbing to integrate CRUD-specific refresh/reload semantics and side loads (dropdown data)
  • ➖ May force more refactoring than warranted for this PR

Recommendation: The PR’s mixed approach is reasonable for incremental adoption (usePaginatedFetch for read-only tabs; manual token-stack around useCrudTab for CRUD tabs). Consider a follow-up to consolidate cursor navigation logic (either enhancing useCrudTab or adopting usePaginatedFetch more broadly) to reduce duplicated token-stack code in multiple tab components.

Files changed (34) +1822 / -215

Enhancement (16) +558 / -114
CatalogApi.tsAdd PaginationParams to CatalogApi list methods +6/-3

Add PaginationParams to CatalogApi list methods

• Updates CatalogApi interface signatures to accept optional PaginationParams for service types, catalog items, and instances.

workspaces/dcm/plugins/dcm-common/src/clients/CatalogApi.ts

CatalogClient.tsAdd cursor pagination query building to CatalogClient lists +20/-6

Add cursor pagination query building to CatalogClient lists

• Extends listServiceTypes/listCatalogItems/listCatalogItemInstances to accept PaginationParams and append a shared query string via buildPaginationQuery.

workspaces/dcm/plugins/dcm-common/src/clients/CatalogClient.ts

PolicyManagerApi.tsAdd PaginationParams to PolicyManagerApi.listPolicies +2/-1

Add PaginationParams to PolicyManagerApi.listPolicies

• Changes PolicyManagerApi.listPolicies to accept optional PaginationParams, enabling cursor pagination from callers.

workspaces/dcm/plugins/dcm-common/src/clients/PolicyManagerApi.ts

PolicyManagerClient.tsImplement paginated listPolicies in PolicyManagerClient +4/-2

Implement paginated listPolicies in PolicyManagerClient

• Implements listPolicies(params) and appends cursor query parameters using buildPaginationQuery.

workspaces/dcm/plugins/dcm-common/src/clients/PolicyManagerClient.ts

ProvidersApi.tsAdd PaginationParams to ProvidersApi.listProviders +2/-1

Add PaginationParams to ProvidersApi.listProviders

• Changes ProvidersApi.listProviders to accept optional PaginationParams, enabling cursor pagination from callers.

workspaces/dcm/plugins/dcm-common/src/clients/ProvidersApi.ts

ProvidersClient.tsImplement paginated listProviders in ProvidersClient +4/-2

Implement paginated listProviders in ProvidersClient

• Implements listProviders(params) and appends cursor query parameters using buildPaginationQuery.

workspaces/dcm/plugins/dcm-common/src/clients/ProvidersClient.ts

index.tsExport buildPaginationQuery from dcm-common +1/-0

Export buildPaginationQuery from dcm-common

• Re-exports buildPaginationQuery so all consumers can share consistent pagination URL construction.

workspaces/dcm/plugins/dcm-common/src/index.ts

common.tsAdd shared PaginationParams type +13/-0

Add shared PaginationParams type

• Introduces PaginationParams (max_page_size, page_token) as a shared type for cursor-pagination list endpoints.

workspaces/dcm/plugins/dcm-common/src/types/common.ts

buildPaginationQuery.tsAdd shared buildPaginationQuery utility +32/-0

Add shared buildPaginationQuery utility

• Adds a utility to build query strings for max_page_size and page_token, returning an empty string when unset.

workspaces/dcm/plugins/dcm-common/src/utils/buildPaginationQuery.ts

CursorPaginationControls.tsxAdd reusable Previous/Next controls for cursor pagination +80/-0

Add reusable Previous/Next controls for cursor pagination

• Introduces a small UI component that renders Previous/Next buttons and handles disabled/loading states for cursor-based pagination.

workspaces/dcm/plugins/dcm/src/components/CursorPaginationControls.tsx

DcmCrudTabLayout.tsxSupport cursor pagination mode in CRUD tab layout +68/-29

Support cursor pagination mode in CRUD tab layout

• Makes client-side Table paging optional and adds a cursorPagination prop to render CursorPaginationControls under the table when server-side paging is used.

workspaces/dcm/plugins/dcm/src/components/DcmCrudTabLayout.tsx

dcmTabListHelpers.tsxAdd cursor pagination option to DcmSearchTableCard +67/-26

Add cursor pagination option to DcmSearchTableCard

• Adds an optional cursorPagination prop to disable the Table pager and render CursorPaginationControls for read-only list cards.

workspaces/dcm/plugins/dcm/src/components/dcmTabListHelpers.tsx

useCrudTab.tsExtend useCrudTab to support server-side paged load results +28/-3

Extend useCrudTab to support server-side paged load results

• Allows loadFn to return either a plain array (client-side paging) or a PagedLoadResult with nextPageToken; exposes nextPageToken in the hook result and clears it on errors.

workspaces/dcm/plugins/dcm/src/hooks/useCrudTab.ts

usePaginatedFetch.tsIntroduce generic usePaginatedFetch cursor pagination hook +183/-0

Introduce generic usePaginatedFetch cursor pagination hook

• Adds a reusable cursor pagination hook with a token stack for Previous navigation, plus reset/refresh helpers and robust error handling.

workspaces/dcm/plugins/dcm/src/hooks/usePaginatedFetch.ts

ServiceTypesTabContent.tsxConvert Service Types tab to usePaginatedFetch cursor pagination +46/-41

Convert Service Types tab to usePaginatedFetch cursor pagination

• Replaces prior load/page slicing logic with usePaginatedFetch, resets cursor on search, updates retry behavior, and renders cursor controls under the table.

workspaces/dcm/plugins/dcm/src/pages/service-types/ServiceTypesTabContent.tsx

ref.tsAdd translations for cursor pagination buttons +2/-0

Add translations for cursor pagination buttons

• Adds i18n strings for Previous/Next pagination controls used by CursorPaginationControls.

workspaces/dcm/plugins/dcm/src/translations/ref.ts

Bug fix (6) +326 / -87
catalog.tsMake next_page_token optional in catalog list types +3/-3

Make next_page_token optional in catalog list types

• Aligns ServiceTypeList/CatalogItemList/CatalogItemInstanceList with backend behavior where next_page_token may be absent on single-page results.

workspaces/dcm/plugins/dcm-common/src/types/catalog.ts

CatalogItemInstancesTabContent.tsxAdd cursor pagination to Catalog Item Instances tab +67/-12

Add cursor pagination to Catalog Item Instances tab

• Switches instance listing to server-side cursor pagination using useCrudTab PagedLoadResult, persists page size, caps catalog-item dropdown fetch to 25, and renders cursor controls via DcmCrudTabLayout.

workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/CatalogItemInstancesTabContent.tsx

CatalogItemsTabContent.tsxAdd cursor pagination to Catalog Items tab +68/-10

Add cursor pagination to Catalog Items tab

• Switches item listing to server-side cursor pagination, persists page size, caps service-type dropdown fetch to 25, and wires Next/Previous controls into DcmCrudTabLayout.

workspaces/dcm/plugins/dcm/src/pages/catalog-items/CatalogItemsTabContent.tsx

PoliciesTabContent.tsxAdd cursor pagination to Policies tab +72/-10

Add cursor pagination to Policies tab

• Updates listPolicies calls to pass {page_token,max_page_size}, stores cursor state (current token + stack), resets cursor on search changes, and renders cursor controls in the CRUD layout.

workspaces/dcm/plugins/dcm/src/pages/policies/PoliciesTabContent.tsx

ProvidersTabContent.tsxAdd cursor pagination to Providers tab +70/-14

Add cursor pagination to Providers tab

• Updates listProviders calls to pass {page_token,max_page_size}, caps service-type dropdown to 25, manages cursor token stack for Next/Previous, and renders cursor controls in the CRUD layout.

workspaces/dcm/plugins/dcm/src/pages/providers/ProvidersTabContent.tsx

ResourcesTabContent.tsxConvert Resources tab to usePaginatedFetch cursor pagination +46/-38

Convert Resources tab to usePaginatedFetch cursor pagination

• Replaces single-shot loading/client-side slicing with usePaginatedFetch, resets cursor on search, adjusts empty/error/loading rendering, and enables cursor controls in DcmSearchTableCard.

workspaces/dcm/plugins/dcm/src/pages/resources/ResourcesTabContent.tsx

Tests (5) +836 / -1
useCrudTab.test.tsAdd tests for paged load results in useCrudTab +45/-0

Add tests for paged load results in useCrudTab

• Extends hook tests to validate PagedLoadResult handling, nextPageToken extraction/defaulting, and token reset on failure.

workspaces/dcm/plugins/dcm/src/hooks/useCrudTab.test.ts

usePaginatedFetch.test.tsAdd unit tests for generic cursor pagination hook +210/-0

Add unit tests for generic cursor pagination hook

• Adds test coverage for initial load, next/prev navigation, refresh behavior, error handling, and cursor resets.

workspaces/dcm/plugins/dcm/src/hooks/usePaginatedFetch.test.ts

PoliciesTabContent.test.tsxAdd cursor pagination tests for Policies tab +81/-1

Add cursor pagination tests for Policies tab

• Updates mocks and adds tests asserting pagination params on mount, Next/Previous button state, and page_token propagation after navigation.

workspaces/dcm/plugins/dcm/src/pages/policies/PoliciesTabContent.test.tsx

ProvidersTabContent.test.tsxAdd cursor pagination tests for Providers tab +269/-0

Add cursor pagination tests for Providers tab

• Adds a full test suite covering pagination params, dropdown max_page_size=25, load/error states, and Next/Previous navigation/token passing.

workspaces/dcm/plugins/dcm/src/pages/providers/ProvidersTabContent.test.tsx

ResourcesTabContent.test.tsxAdd cursor pagination tests for Resources tab +231/-0

Add cursor pagination tests for Resources tab

• Adds tests verifying pagination params on mount, retry behavior, and Next/Previous navigation states and page_token propagation.

workspaces/dcm/plugins/dcm/src/pages/resources/ResourcesTabContent.test.tsx

Other (7) +102 / -13
server-side-pagination-all-tabs.mdChangeset documenting server-side pagination rollout +28/-0

Changeset documenting server-side pagination rollout

• Adds a changeset describing the cursor-pagination expansion to all six tabs, API interface updates, dropdown page-size caps, and new/updated tests.

workspaces/dcm/.changeset/server-side-pagination-all-tabs.md

knip-report.mdAdd knip unused-deps report (app) +17/-0

Add knip unused-deps report (app)

• Introduces a Knip report artifact listing unused dependencies and devDependencies for the DCM app workspace.

workspaces/dcm/packages/app/knip-report.md

knip-report.mdAdd knip unused-deps report (backend) +15/-0

Add knip unused-deps report (backend)

• Introduces a Knip report artifact listing unused dependencies/devDependencies for the DCM backend workspace.

workspaces/dcm/packages/backend/knip-report.md

knip-report.mdAdd knip report placeholder (dcm-backend) +1/-0

Add knip report placeholder (dcm-backend)

• Adds a minimal Knip report file for the dcm-backend plugin workspace.

workspaces/dcm/plugins/dcm-backend/knip-report.md

knip-report.mdAdd knip report placeholder (dcm-common) +1/-0

Add knip report placeholder (dcm-common)

• Adds a minimal Knip report file for the dcm-common plugin workspace.

workspaces/dcm/plugins/dcm-common/knip-report.md

report.api.mdPublish pagination APIs in the generated API report +26/-13

Publish pagination APIs in the generated API report

• Updates the public API report to include PaginationParams, buildPaginationQuery, and pagination-capable list methods; also makes next_page_token optional in list types.

workspaces/dcm/plugins/dcm-common/report.api.md

knip-report.mdAdd knip unused-deps report (dcm frontend plugin) +14/-0

Add knip unused-deps report (dcm frontend plugin)

• Introduces a Knip report artifact listing unused dependencies/devDependencies for the dcm frontend plugin workspace.

workspaces/dcm/plugins/dcm/knip-report.md

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.69492% with 55 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.19%. Comparing base (8c14679) to head (5fad948).
⚠️ Report is 21 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4155      +/-   ##
==========================================
+ Coverage   58.06%   58.19%   +0.13%     
==========================================
  Files        2411     2415       +4     
  Lines       96367    96541     +174     
  Branches    26856    26895      +39     
==========================================
+ Hits        55953    56181     +228     
+ Misses      40215    40130      -85     
- Partials      199      230      +31     
Flag Coverage Δ *Carryforward flag
adoption-insights 84.55% <ø> (ø) Carriedforward from 11a44cf
ai-integrations 69.71% <ø> (ø) Carriedforward from 11a44cf
app-defaults 69.79% <ø> (ø) Carriedforward from 11a44cf
augment 46.67% <ø> (ø) Carriedforward from 11a44cf
boost 76.77% <ø> (ø) Carriedforward from 11a44cf
bulk-import 72.56% <ø> (ø) Carriedforward from 11a44cf
cost-management 13.55% <ø> (ø) Carriedforward from 11a44cf
dcm 67.21% <76.69%> (+6.49%) ⬆️
extensions 56.59% <ø> (ø) Carriedforward from 11a44cf
global-floating-action-button 71.18% <ø> (ø) Carriedforward from 11a44cf
global-header 66.50% <ø> (ø) Carriedforward from 11a44cf
homepage 47.50% <ø> (ø) Carriedforward from 11a44cf
install-dynamic-plugins 59.95% <ø> (ø) Carriedforward from 11a44cf
intelligent-assistant 74.61% <ø> (ø) Carriedforward from 11a44cf
konflux 91.98% <ø> (ø) Carriedforward from 11a44cf
lightspeed 69.02% <ø> (ø) Carriedforward from 11a44cf
mcp-integrations 83.40% <ø> (ø) Carriedforward from 11a44cf
orchestrator 66.87% <ø> (ø) Carriedforward from 11a44cf
quickstart 63.74% <ø> (ø) Carriedforward from 11a44cf
sandbox 79.56% <ø> (ø) Carriedforward from 11a44cf
scorecard 85.45% <ø> (ø) Carriedforward from 11a44cf
theme 88.52% <ø> (ø) Carriedforward from 11a44cf
translations 5.12% <ø> (ø) Carriedforward from 11a44cf
x2a 79.20% <ø> (ø) Carriedforward from 11a44cf

*This pull request uses carry forward flags. Click here to find out more.


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8c14679...5fad948. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@rhdh-qodo-merge

rhdh-qodo-merge Bot commented Aug 4, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. resetCursor traps pagination ✓ Resolved 🐞 Bug ≡ Correctness
Description
usePaginatedFetch.resetCursor (and related search handlers in cursor‑paginated CRUD tabs) clears
the current cursor/nextToken without triggering a reload, and several tabs call this on every
search change. This can leave later-page data on screen while pagination state is reset
(Next/Previous disabled or navigation jumping incorrectly), and the cursor for the currently
displayed page is lost.
Code

workspaces/dcm/plugins/dcm/src/hooks/usePaginatedFetch.ts[R165-168]

+    currentTokenRef.current = undefined;
+    tokenStackRef.current = [];
+    setTokenStack([]);
+    setNextToken('');
Relevance

●●● Strong

Correctness issue in new pagination flow; team has accepted similar hook-state hardening fixes
before.

PR-#3342
PR-#2950

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The cited logic for resetCursor explicitly clears currentTokenRef and nextToken without
fetching new data, and the Service Types and Resources tabs invoke it on each search change, so the
pagination state is effectively reset to “page 1 with no next page” while the data remains
whatever page was previously loaded. In the CRUD tabs, handleSearchChange similarly clears
currentTokenRef without a reload; later, goNext pushes currentTokenRef.current ?? '' onto the
token stack and goPrev uses that stack token to restore the previous page token, so after
searching on page > 1 the pushed value becomes '' and goPrev can navigate back to the first page
token (undefined) instead of returning to the page that was displayed when the search was applied.

workspaces/dcm/plugins/dcm/src/hooks/usePaginatedFetch.ts[156-169]
workspaces/dcm/plugins/dcm/src/pages/service-types/ServiceTypesTabContent.tsx[64-70]
workspaces/dcm/plugins/dcm/src/pages/resources/ResourcesTabContent.tsx[62-68]
workspaces/dcm/plugins/dcm/src/pages/providers/ProvidersTabContent.tsx[121-152]
workspaces/dcm/plugins/dcm/src/pages/policies/PoliciesTabContent.tsx[139-177]
workspaces/dcm/plugins/dcm/src/pages/catalog-items/CatalogItemsTabContent.tsx[156-187]
workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/CatalogItemInstancesTabContent.tsx[133-164]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Search changes in cursor-paginated tabs reset cursor/navigation state (`currentTokenRef`, `nextToken`, and/or token stack) without re-fetching, which makes pagination state inconsistent with the currently displayed `data` and can also break back/forward navigation invariants.

## Issue Context
- `usePaginatedFetch.resetCursor()` clears `currentTokenRef` and `nextToken` but does not fetch new data.
- Service Types and Resources call `resetCursor()` in a `useEffect` whenever `search` changes, so typing in the search box can leave the user looking at page N rows while pagination behaves like page 1.
- In cursor-paginated CRUD tabs, `handleSearchChange` clears `currentTokenRef.current` without reloading; since `goNext` pushes `currentTokenRef.current ?? ''` to the token stack and `goPrev` relies on that stack to restore the previous page token, clearing the current token on search can cause Next/Previous to jump to the wrong page (e.g., Previous goes to page 1 rather than the pre-search page).

## Fix Focus Areas
- workspaces/dcm/plugins/dcm/src/hooks/usePaginatedFetch.ts[156-169]
- workspaces/dcm/plugins/dcm/src/pages/service-types/ServiceTypesTabContent.tsx[64-70]
- workspaces/dcm/plugins/dcm/src/pages/resources/ResourcesTabContent.tsx[62-68]
- workspaces/dcm/plugins/dcm/src/pages/providers/ProvidersTabContent.tsx[124-152]
- workspaces/dcm/plugins/dcm/src/pages/policies/PoliciesTabContent.tsx[149-177]
- workspaces/dcm/plugins/dcm/src/pages/catalog-items/CatalogItemsTabContent.tsx[159-187]
- workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/CatalogItemInstancesTabContent.tsx[136-164]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context used
✅ Compliance rules (platform): 11 rules
✅ Cross-repo context
  Explored: repo: redhat-developer/rhdh (sha: 3875f708)
  Not relevant to this PR: redhat-developer/rhdh-chart
  Not relevant to this PR: redhat-developer/rhdh-operator
  Not relevant to this PR: redhat-developer/rhdh-local

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

@rhdh-qodo-merge rhdh-qodo-merge Bot added documentation Improvements or additions to documentation enhancement New feature or request Tests labels Aug 4, 2026
@asmasarw asmasarw changed the title Add Server Side Paginations [DCM] Server-side pagination not implemented Aug 4, 2026
@asmasarw asmasarw changed the title [DCM] Server-side pagination not implemented FLPATH-4285 | [DCM] Server-side pagination not implemented Aug 4, 2026

@mareklibra mareklibra left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • No usePaginatedCrudTab unit tests, no buildPaginationQuery tests, no search+resetCursor regression, and ProvidersClient.test still only asserts unpaginated GET /providers. Worth adding at least query-param client coverage and the search/Next regression above.

  • Cursor controls default to [5, 15, 25] while the Table pager uses [5, 10, 25]. Align them unless 15 is intentional.

  • Providers/Catalog loadFns re-fetch service-types/catalog-items (capped at 25) on every Next/Prev. Load dropdown options once (separate effect) to avoid redundant traffic.

fetch();
}, [fetch]);

const resetCursor = useCallback(() => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resetCursor() clears nextToken without a refetch, and Service Types / Resources call it on every search change. After any search interaction, Next is permanently disabled until remount/page-size change (even after clearing the query) because the token is never restored.

Please either:

  1. Remove these resetCursor()-on-search effects (keep Prev/Next tied to the loaded page), or
  2. Call resetToFirstPage() so search resets to page 1 with a real reload.

Also add a regression test: load page 1 with nextPageToken, change search, clear search, assert Next is still enabled / token preserved or page 1 re-fetched.


// When the search changes, reset cursor navigation state (no API call
// needed — search filters the current page client-side).
const handleSearchChange = useCallback(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It clears currentTokenRef + token stack but leaves nextPageToken and the displayed page data as-is. Comments say this “resets to page 1”, but the user can still be viewing page N rows with Next enabled. Prefer not touching cursor state for client-side filter, or call a real resetToFirstPage + reload.

items: r.providers ?? [],
nextPageToken: r.next_page_token,
})),
catalogApi

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capping form dropdowns at max_page_size: 25 does not “prevent incomplete lists” (changeset wording). API default is already 100; 25 truncates sooner with no “load more” / typeahead. Prefer 100, full pagination of options, or an explicit incomplete-list indicator.

const optsRef = useRef(options);
optsRef.current = options;

const crud = useCrudTab<T, F>({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usePaginatedCrudTab calls usePersistedPageSize(storageKey) and then spreads ...options (including storageKey) into useCrudTab, which calls usePersistedPageSize again on the same key. Two React states back the same localStorage entry; only the outer one is updated on page-size change. Destructure storageKey out before spreading into useCrudTab.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

items.length === 0 always renders the illustration empty-state and drops cursor controls. After deleting the last row on page 2+, or an empty cursor page with hasPrev, the user cannot go Previous. In cursor mode, treat “empty current page + hasPrev” as an empty table with pagination (or auto-goPrev), not the global empty state.

}

/** Props that can be passed directly to {@link DcmCrudTabLayout}'s `cursorPagination`. */
export interface CursorPaginationProps {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three near-identical pagination prop types will drift. Export one from CursorPaginationControls.tsx and reuse it.

},
}));

export type CursorPaginationControlsProps = Readonly<{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three near-identical pagination prop types will drift. Export one from CursorPaginationControls.tsx and reuse it.

);
}

export type CursorPaginationProps = Readonly<{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three near-identical pagination prop types will drift. Export one from CursorPaginationControls.tsx and reuse it.


Previously only Service Types, Catalog Items, and Catalog Item Instances fetched data page-by-page from the backend. Providers, Policies, and Resources loaded everything in a single call and silently lost records beyond the first page.

- **Providers** and **Policies** — converted to the `useCrudTab` + manual token-stack pattern (same as Catalog Items). Next / Previous buttons appear below the table; search resets to page 1 client-side without an extra round-trip.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changeset still says Providers/Policies use a “manual token-stack around useCrudTab”, but the code uses usePaginatedCrudTab. Please align the changeset with the final design.

@asmasarw
asmasarw requested a review from mareklibra August 4, 2026 13:24

@mareklibra mareklibra left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding new comments. Something is left from the last time.

nextPageToken: r.next_page_token,
})),
catalogApi
.listCatalogItems({ max_page_size: 25 })

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catalog Items / Providers were moved to a one-shot mount fetch with
max_page_size: 100, but Catalog Item Instances still does
listCatalogItems({ max_page_size: 25 }) inside loadFn via
Promise.all — so every Next/Prev re-hits catalog-items and the create
dropdown silently truncates after 25.

Please mirror Providers/Catalog Items: load catalog items once in a
useEffect with max_page_size: 100 (or paginate/typeahead), and keep
loadFn to instances only.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort of:

useEffect(() => {
  catalogApi
    .listCatalogItems({ max_page_size: 100 })
    .then(r => setCatalogItems(r.results ?? []))
    .catch(() => {});
}, [catalogApi]);

const crud = usePaginatedCrudTab({
  loadFn: ({ pageToken, pageSize: ps }) =>
    catalogApi
      .listCatalogItemInstances({ page_token: pageToken, max_page_size: ps })
      .then(r => ({
        items: r.results ?? [],
        nextPageToken: r.next_page_token,
      })),
  // ...
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also update the changeset line that only mentions Providers/Catalog Items dropdowns.

* ...
* />
*/
export function usePaginatedCrudTab<T, F extends Record<string, unknown>>(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usePaginatedCrudTab is the shared cursor stack for four CRUD tabs, but
there’s still no dedicated unit test file. Tab tests help, but they won’t
catch hook regressions (token stack / page-size reset / storageKey
stripping) as cheaply.

Please add usePaginatedCrudTab.test.ts covering: initial load params,
goNext/goPrev token passing, handlePageSizeChange → page 1 reload, and
search leaving cursor/next token untouched.

catalogApi
.listServiceTypes({ max_page_size: 100 })
.then(r => setServiceTypes(r.results ?? []))
.catch(() => {});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.catch(() => {}) on the mount-time service-types fetch leaves create/edit
forms with an empty dropdown and no error. At least surface a non-blocking
alert or log via the app logger so failures aren’t silent.

* Backstage Table's built-in pager. The table is rendered with `paging:
* false` and {@link CursorPaginationControls} is shown below it.
*/
cursorPagination?: {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.catch(() => {}) on the mount-time service-types fetch leaves create/edit
forms with an empty dropdown and no error. At least surface a non-blocking
alert or log via the app logger so failures aren’t silent.

/>
{cursorPagination ? (
<CursorPaginatedTable<T>
data={filtered}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In cursor mode the table now uses data={filtered} (current server page).
The card title still uses filtered.length, so the number is page-local,
not a dataset total. Consider dropping the count, labeling it as “showing”,
or only showing it when a real total exists.

@sonarqubecloud

sonarqubecloud Bot commented Aug 5, 2026

Copy link
Copy Markdown

@asmasarw
asmasarw requested a review from mareklibra August 5, 2026 12:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request Tests workspace/dcm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants