diff --git a/.changeset/perfect-chefs-act.md b/.changeset/perfect-chefs-act.md new file mode 100644 index 0000000000..08da157dc7 --- /dev/null +++ b/.changeset/perfect-chefs-act.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': minor +--- + +Number of results is now directly added as the field `totalItems` on `useEntityList`. diff --git a/.changeset/rare-worms-sort.md b/.changeset/rare-worms-sort.md new file mode 100644 index 0000000000..6b2175d6d9 --- /dev/null +++ b/.changeset/rare-worms-sort.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': minor +--- + +Adds a title to the `PaginatedCatalogTable` for better visibility on what you're viewing. diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index dfb5780a2c..ce2502691f 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -306,6 +306,7 @@ export type EntityListContextProps< next?: () => void; prev?: () => void; }; + totalItems?: number; }; // @public diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index c2328a1aef..bc8e6b6038 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -235,6 +235,7 @@ describe('', () => { await waitFor(() => { expect(result.current.entities.length).toBe(1); }); + expect(result.current.totalItems).toBe(1); await expect(() => waitFor(() => { @@ -251,6 +252,7 @@ describe('', () => { await waitFor(() => { expect(result.current.backendEntities.length).toBeGreaterThan(0); }); + expect(result.current.totalItems).toBe(2); expect(result.current.backendEntities.length).toBe(2); expect(mockCatalogApi.getEntities).toHaveBeenCalledTimes(1); @@ -276,6 +278,8 @@ describe('', () => { }); expect(result.current.backendEntities.length).toBe(2); + expect(result.current.totalItems).toBe(2); + mockCatalogApi.getEntities!.mockRejectedValueOnce('error'); act(() => { result.current.updateFilters({ kind: new EntityKindFilter('api') }); @@ -455,6 +459,8 @@ describe('', () => { orderFields, }); }); + + expect(result.current.totalItems).toBe(10); }); it('returns an error on catalogApi failure', async () => { diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index abb366a568..c81a399040 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -108,6 +108,8 @@ export type EntityListContextProps< next?: () => void; prev?: () => void; }; + + totalItems?: number; }; /** @@ -124,6 +126,7 @@ type OutputState = { entities: Entity[]; backendEntities: Entity[]; pageInfo?: QueryEntitiesResponse['pageInfo']; + totalItems?: number; }; /** @@ -223,6 +226,7 @@ export const EntityListProvider = ( backendEntities: response.items, entities: response.items.filter(entityFilter), pageInfo: response.pageInfo, + totalItems: response.totalItems, }); } } else { @@ -243,6 +247,7 @@ export const EntityListProvider = ( backendEntities: response.items, entities: response.items.filter(entityFilter), pageInfo: response.pageInfo, + totalItems: response.totalItems, }); } } @@ -262,16 +267,20 @@ export const EntityListProvider = ( const response = await catalogApi.getEntities({ filter: backendFilter, }); + const entities = response.items.filter(entityFilter); setOutputState({ appliedFilters: requestedFilters, backendEntities: response.items, - entities: response.items.filter(entityFilter), + entities, + totalItems: entities.length, }); } else { + const entities = outputState.backendEntities.filter(entityFilter); setOutputState({ appliedFilters: requestedFilters, backendEntities: outputState.backendEntities, - entities: outputState.backendEntities.filter(entityFilter), + entities, + totalItems: entities.length, }); } } @@ -352,6 +361,7 @@ export const EntityListProvider = ( loading, error, pageInfo, + totalItems: outputState.totalItems, }), [outputState, updateFilters, queryParameters, loading, error, pageInfo], ); diff --git a/plugins/catalog-react/src/testUtils/providers.tsx b/plugins/catalog-react/src/testUtils/providers.tsx index 8ab6d118a9..6b88168207 100644 --- a/plugins/catalog-react/src/testUtils/providers.tsx +++ b/plugins/catalog-react/src/testUtils/providers.tsx @@ -71,6 +71,8 @@ export function MockEntityListContextProvider< loading: value?.loading ?? false, queryParameters: value?.queryParameters ?? defaultValues.queryParameters, error: value?.error, + totalItems: + value?.totalItems ?? (value?.entities ?? defaultValues.entities).length, }), [value, defaultValues, filters, updateFilters], ); diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 7da12d7138..8aa5b9e376 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -88,7 +88,8 @@ export const CatalogTable = (props: CatalogTableProps) => { } = props; const { isStarredEntity, toggleStarredEntity } = useStarredEntities(); const entityListContext = useEntityList(); - const { loading, error, entities, filters, pageInfo } = entityListContext; + const { loading, error, entities, filters, pageInfo, totalItems } = + entityListContext; const enablePagination = !!pageInfo; const tableColumns = useMemo( @@ -175,7 +176,7 @@ export const CatalogTable = (props: CatalogTableProps) => { .filter(s => s) .join(' '); - const title = `${titleDisplay} (${entities.length})`; + const title = `${titleDisplay} (${totalItems})`; const actions = props.actions || defaultActions; const options = { actionsColumnIndex: -1, @@ -216,7 +217,7 @@ export const CatalogTable = (props: CatalogTableProps) => { pageSizeOptions: [20, 50, 100], ...options, }} - title={`${titleDisplay} (${entities.length})`} + title={title} data={rows} actions={actions} subtitle={subtitle} diff --git a/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.test.tsx index eb1acc0cee..976cdff667 100644 --- a/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.test.tsx +++ b/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.test.tsx @@ -14,14 +14,16 @@ * limitations under the License. */ import React, { ReactNode } from 'react'; -import { fireEvent, render } from '@testing-library/react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; import { PaginatedCatalogTable } from './PaginatedCatalogTable'; import { screen } from '@testing-library/react'; import { CatalogTableRow } from './types'; +import { renderInTestApp } from '@backstage/test-utils'; import { + EntityKindFilter, + MockEntityListContextProvider, DefaultEntityFilters, EntityListContextProps, - MockEntityListContextProvider, } from '@backstage/plugin-catalog-react'; describe('PaginatedCatalogTable', () => { @@ -134,4 +136,32 @@ describe('PaginatedCatalogTable', () => { fireEvent.click(prevButton); expect(fn).toHaveBeenCalled(); }); + + it('should display entity names when loading has finished and no error occurred', async () => { + await renderInTestApp( + e.entity), + totalItems: data.length, + filters: { + kind: new EntityKindFilter('component'), + }, + }} + > + + , + ); + + expect(screen.getByText(/component-0/)).toBeInTheDocument(); + expect(screen.getByText(/component-50/)).toBeInTheDocument(); + expect(screen.getByText(/component-99/)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(/My title/)).toBeInTheDocument(); + }); + }); }); diff --git a/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.tsx index 0311cd22ca..fa40756a44 100644 --- a/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.tsx @@ -32,11 +32,12 @@ type PaginatedCatalogTableProps = { * @internal */ export function PaginatedCatalogTable(props: PaginatedCatalogTableProps) { - const { columns, data, next, prev } = props; + const { columns, data, next, prev, title, isLoading } = props; const { updateFilters } = useEntityList(); return (