From 992671e2c8acbef458edab646da4372033cfa203 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Tue, 21 Nov 2023 12:13:03 +0100 Subject: [PATCH] catalog: toggle pagination through props Signed-off-by: Vincenzo Scamporlino --- .changeset/silly-numbers-wash.md | 14 ++++--- plugins/catalog-react/api-report.md | 13 +++--- plugins/catalog-react/src/hooks/index.ts | 1 + .../src/hooks/useEntityListProvider.test.tsx | 42 +++++++++---------- .../src/hooks/useEntityListProvider.tsx | 18 ++++---- plugins/catalog/api-report.md | 10 +++-- .../CatalogPage/DefaultCatalogPage.tsx | 17 ++++---- 7 files changed, 66 insertions(+), 49 deletions(-) diff --git a/.changeset/silly-numbers-wash.md b/.changeset/silly-numbers-wash.md index ba437006dd..c48b0dcc7a 100644 --- a/.changeset/silly-numbers-wash.md +++ b/.changeset/silly-numbers-wash.md @@ -2,13 +2,17 @@ '@backstage/plugin-catalog': patch --- -Added experimental pagination support to `CatalogIndexPage` +Added pagination support to `CatalogIndexPage` -CatalogIndexPage now offers an optional pagination feature, designed to accommodate adopters managing extensive catalogs. This new capability allows for better handling of large amounts of data. +`CatalogIndexPage` now offers an optional pagination feature, designed to accommodate adopters managing extensive catalogs. This new capability allows for better handling of large amounts of data. -To activate the pagination mode, simply update your configuration as follows: +To activate the pagination mode, simply update your `App.tsx` as follows: ```diff - catalog: -+ experimentalPagination: true + const routes = ( + + ... +- } /> ++ } /> + ... ``` diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 9c1a6249ae..7e5e2fc5e7 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -297,11 +297,7 @@ export const EntityListProvider: ( // @public (undocumented) export type EntityListProviderProps = PropsWithChildren<{ - enablePagination?: - | boolean - | { - limit?: number; - }; + pagination?: Pagination; }>; // @public (undocumented) @@ -657,6 +653,13 @@ export class MockStarredEntitiesApi implements StarredEntitiesApi { toggleStarred(entityRef: string): Promise; } +// @public (undocumented) +export type Pagination = + | boolean + | { + limit?: number; + }; + // @public export interface StarredEntitiesApi { starredEntitie$(): Observable>; diff --git a/plugins/catalog-react/src/hooks/index.ts b/plugins/catalog-react/src/hooks/index.ts index c3021f8992..c2f9604de9 100644 --- a/plugins/catalog-react/src/hooks/index.ts +++ b/plugins/catalog-react/src/hooks/index.ts @@ -33,6 +33,7 @@ export type { DefaultEntityFilters, EntityListContextProps, EntityListProviderProps, + Pagination, } from './useEntityListProvider'; export { useEntityTypeFilter } from './useEntityTypeFilter'; export { useRelatedEntities } from './useRelatedEntities'; diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index 5838c79cdd..f6881d4a80 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -87,7 +87,7 @@ const mockCatalogApi: Partial> = { }; const createWrapper = - (options: { location?: string; enablePagination: boolean }) => + (options: { location?: string; pagination: boolean }) => (props: PropsWithChildren) => { const InitialFiltersWrapper = ({ children }: PropsWithChildren) => { const { updateFilters } = useEntityList(); @@ -111,7 +111,7 @@ const createWrapper = [alertApiRef, { post: jest.fn() }], ]} > - + {props.children} @@ -121,7 +121,7 @@ const createWrapper = describe('', () => { const origReplaceState = window.history.replaceState; - const enablePagination = false; + const pagination = false; beforeEach(() => { window.history.replaceState = jest.fn(); @@ -136,7 +136,7 @@ describe('', () => { it('should send backend filters', async () => { const { result } = renderHook(() => useEntityList(), { - wrapper: createWrapper({ enablePagination }), + wrapper: createWrapper({ pagination }), }); await waitFor(() => { @@ -152,7 +152,7 @@ describe('', () => { it('resolves frontend filters', async () => { const { result } = renderHook(() => useEntityList(), { - wrapper: createWrapper({ enablePagination }), + wrapper: createWrapper({ pagination }), initialProps: { userFilter: 'all', }, @@ -178,7 +178,7 @@ describe('', () => { const { result } = renderHook(() => useEntityList(), { wrapper: createWrapper({ location: `/catalog?${query}`, - enablePagination, + pagination, }), }); @@ -193,7 +193,7 @@ describe('', () => { it('does not fetch when only frontend filters change', async () => { const { result } = renderHook(() => useEntityList(), { - wrapper: createWrapper({ enablePagination }), + wrapper: createWrapper({ pagination }), }); await waitFor(() => { @@ -220,7 +220,7 @@ describe('', () => { it('debounces multiple filter changes', async () => { const { result } = renderHook(() => useEntityList(), { - wrapper: createWrapper({ enablePagination }), + wrapper: createWrapper({ pagination }), }); await waitFor(() => { @@ -243,7 +243,7 @@ describe('', () => { it('returns an error on catalogApi failure', async () => { const { result } = renderHook(() => useEntityList(), { - wrapper: createWrapper({ enablePagination }), + wrapper: createWrapper({ pagination }), }); await waitFor(() => { @@ -262,7 +262,7 @@ describe('', () => { it('returns an empty pageInfo', async () => { const { result } = renderHook(() => useEntityList(), { - wrapper: createWrapper({ enablePagination }), + wrapper: createWrapper({ pagination }), }); await waitFor(() => { expect(mockCatalogApi.getEntities).toHaveBeenCalled(); @@ -272,9 +272,9 @@ describe('', () => { }); }); -describe('', () => { +describe('', () => { const origReplaceState = window.history.replaceState; - const enablePagination = true; + const pagination = true; const limit = 20; const orderFields = [{ field: 'metadata.name', order: 'asc' }]; @@ -291,7 +291,7 @@ describe('', () => { it('should send backend filters', async () => { const { result } = renderHook(() => useEntityList(), { - wrapper: createWrapper({ enablePagination }), + wrapper: createWrapper({ pagination }), }); await waitFor(() => { @@ -309,7 +309,7 @@ describe('', () => { it('resolves frontend filters', async () => { const { result } = renderHook(() => useEntityList(), { - wrapper: createWrapper({ enablePagination }), + wrapper: createWrapper({ pagination }), initialProps: { userFilter: 'all', }, @@ -335,7 +335,7 @@ describe('', () => { const { result } = renderHook(() => useEntityList(), { wrapper: createWrapper({ location: `/catalog?${query}`, - enablePagination, + pagination, }), }); @@ -350,7 +350,7 @@ describe('', () => { it('fetch when frontend filters change', async () => { const { result } = renderHook(() => useEntityList(), { - wrapper: createWrapper({ enablePagination }), + wrapper: createWrapper({ pagination }), }); await waitFor(() => { @@ -375,7 +375,7 @@ describe('', () => { it('debounces multiple filter changes', async () => { const { result } = renderHook(() => useEntityList(), { - wrapper: createWrapper({ enablePagination }), + wrapper: createWrapper({ pagination }), }); await waitFor(() => { @@ -400,7 +400,7 @@ describe('', () => { it('returns an error on catalogApi failure', async () => { const { result } = renderHook(() => useEntityList(), { - wrapper: createWrapper({ enablePagination }), + wrapper: createWrapper({ pagination }), }); await waitFor(() => { @@ -425,7 +425,7 @@ describe('', () => { totalItems: 10, }); const { result } = renderHook(() => useEntityList(), { - wrapper: createWrapper({ enablePagination }), + wrapper: createWrapper({ pagination }), }); await waitFor(() => { expect(mockCatalogApi.queryEntities).toHaveBeenCalled(); @@ -439,7 +439,7 @@ describe('', () => { it('returns pageInfo with next function and properly fetch next batch', async () => { const { result } = renderHook(() => useEntityList(), { - wrapper: createWrapper({ enablePagination }), + wrapper: createWrapper({ pagination }), }); await waitFor(() => { expect(mockCatalogApi.queryEntities).toHaveBeenCalled(); @@ -463,7 +463,7 @@ describe('', () => { it('returns pageInfo with prev function and properly fetch prev batch', async () => { const { result } = renderHook(() => useEntityList(), { - wrapper: createWrapper({ enablePagination }), + wrapper: createWrapper({ pagination }), }); await waitFor(() => { expect(mockCatalogApi.queryEntities).toHaveBeenCalled(); diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index 9eaaab7da2..4b7adfd26a 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -130,9 +130,14 @@ type OutputState = { * @public */ export type EntityListProviderProps = PropsWithChildren<{ - enablePagination?: boolean | { limit?: number }; + pagination?: Pagination; }>; +/** + * @public + */ +export type Pagination = boolean | { limit?: number }; + /** * Provides entities and filters for a catalog listing. * @public @@ -153,14 +158,13 @@ export const EntityListProvider = ( const location = useLocation(); const enablePagination = - props.enablePagination === true || - typeof props.enablePagination === 'object'; + props.pagination === true || typeof props.pagination === 'object'; const limit = - props.enablePagination && - typeof props.enablePagination === 'object' && - typeof props.enablePagination.limit === 'number' - ? props.enablePagination.limit + props.pagination && + typeof props.pagination === 'object' && + typeof props.pagination.limit === 'number' + ? props.pagination.limit : 20; const { queryParameters, cursor: initialCursor } = useMemo(() => { diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md index 830a812083..bef24b50d8 100644 --- a/plugins/catalog/api-report.md +++ b/plugins/catalog/api-report.md @@ -158,9 +158,7 @@ export const CatalogTable: { createSystemColumn(): TableColumn; createOwnerColumn(): TableColumn; createSpecTargetsColumn(): TableColumn; - createSpecTypeColumn({ - hidden, - }?: { + createSpecTypeColumn(options?: { hidden: boolean; }): TableColumn; createSpecLifecycleColumn(): TableColumn; @@ -238,6 +236,12 @@ export interface DefaultCatalogPageProps { // (undocumented) ownerPickerMode?: EntityOwnerPickerProps['mode']; // (undocumented) + pagination?: + | boolean + | { + limit?: number; + }; + // (undocumented) tableOptions?: TableProps['options']; } diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx index 68632e5708..21f8351127 100644 --- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx @@ -37,6 +37,7 @@ import { EntityKindPicker, EntityNamespacePicker, EntityOwnerPickerProps, + Pagination, } from '@backstage/plugin-catalog-react'; import React, { ReactNode } from 'react'; import { createComponentRouteRef } from '../../routes'; @@ -47,23 +48,20 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { CatalogTableColumnsFunc } from '../CatalogTable/types'; /** @internal */ -export interface BaseCatalogPageProps { +export type BaseCatalogPageProps = { filters: ReactNode; content?: ReactNode; -} + pagination?: Pagination; +}; /** @internal */ export function BaseCatalogPage(props: BaseCatalogPageProps) { - const { filters, content = } = props; + const { filters, content = , pagination } = props; const orgName = useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage'; const createComponentLink = useRouteRef(createComponentRouteRef); const { t } = useTranslationRef(catalogTranslationRef); - const experimentalPagination = useApi(configApiRef).getOptional( - 'catalog.experimentalPagination', - ) as boolean | { limit: number } | undefined; - return ( @@ -74,7 +72,7 @@ export function BaseCatalogPage(props: BaseCatalogPageProps) { /> All your software catalog entities - + {filters} {content} @@ -98,6 +96,7 @@ export interface DefaultCatalogPageProps { tableOptions?: TableProps['options']; emptyContent?: ReactNode; ownerPickerMode?: EntityOwnerPickerProps['mode']; + pagination?: boolean | { limit?: number }; } export function DefaultCatalogPage(props: DefaultCatalogPageProps) { @@ -108,6 +107,7 @@ export function DefaultCatalogPage(props: DefaultCatalogPageProps) { initialKind = 'component', tableOptions = {}, emptyContent, + pagination, ownerPickerMode, } = props; @@ -133,6 +133,7 @@ export function DefaultCatalogPage(props: DefaultCatalogPageProps) { emptyContent={emptyContent} /> } + pagination={pagination} /> ); }