From 4e759dd9b117a8fc31c6be59694ed6bb36f04cfc Mon Sep 17 00:00:00 2001 From: Aramis Date: Mon, 8 Jan 2024 00:35:51 -0500 Subject: [PATCH 01/13] feat(catalog): Add support for server side text filtering. Signed-off-by: Aramis --- packages/app/src/App.tsx | 5 ++- .../src/service/createRouter.test.ts | 43 ++++++++++++++++++- plugins/catalog-backend/src/service/util.ts | 6 +++ .../src/hooks/useEntityListProvider.tsx | 14 +++++- .../CatalogTable/PaginatedCatalogTable.tsx | 10 +++++ 5 files changed, 74 insertions(+), 4 deletions(-) diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index d2dde95f68..2ea0fefdea 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -177,7 +177,10 @@ const routes = ( }> {homePage} - } /> + } + /> } diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 05ebf6e1a9..809d970612 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -38,7 +38,7 @@ import { import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common/alpha'; import { CatalogProcessingOrchestrator } from '../processing/types'; import { z } from 'zod'; -import { encodeCursor } from './util'; +import { decodeCursor, encodeCursor } from './util'; import { wrapInOpenApiTestServer } from '@backstage/backend-openapi-utils'; import { Server } from 'http'; @@ -265,6 +265,47 @@ describe('createRouter readonly disabled', () => { }); }); + it('parses cursor request with fullTextFilter', async () => { + const items: Entity[] = [ + { apiVersion: 'a', kind: 'b', metadata: { name: 'n' } }, + ]; + + entitiesCatalog.queryEntities.mockResolvedValueOnce({ + items, + totalItems: 100, + pageInfo: { + nextCursor: mockCursor({ fullTextFilter: { term: 'mySearch' } }), + }, + }); + + const cursor = mockCursor({ + totalItems: 100, + isPrevious: false, + fullTextFilter: { term: 'mySearch' }, + }); + + const response = await request(app).get( + `/entities/by-query?cursor=${encodeCursor(cursor)}`, + ); + expect(entitiesCatalog.queryEntities).toHaveBeenCalledTimes(1); + expect(entitiesCatalog.queryEntities).toHaveBeenCalledWith({ + cursor, + }); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + items, + totalItems: 100, + pageInfo: { nextCursor: expect.any(String) }, + }); + const decodedCursor = decodeCursor(response.body.pageInfo.nextCursor); + expect(decodedCursor).toMatchObject({ + isPrevious: false, + fullTextFilter: { + term: 'mySearch', + }, + }); + }); + it('should throw in case of malformed cursor', async () => { const items: Entity[] = [ { apiVersion: 'a', kind: 'b', metadata: { name: 'n' } }, diff --git a/plugins/catalog-backend/src/service/util.ts b/plugins/catalog-backend/src/service/util.ts index af1279a59a..4b041d1a70 100644 --- a/plugins/catalog-backend/src/service/util.ts +++ b/plugins/catalog-backend/src/service/util.ts @@ -106,6 +106,12 @@ export const cursorParser: z.ZodSchema = z.object({ orderFields: z.array( z.object({ field: z.string(), order: z.enum(['asc', 'desc']) }), ), + fullTextFilter: z + .object({ + term: z.string(), + fields: z.array(z.string()).optional(), + }) + .optional(), orderFieldValues: z.array(z.string().or(z.null())), filter: entityFilterParser.optional(), isPrevious: z.boolean(), diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index 1ce87c262e..81bf8fb9e3 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -124,6 +124,7 @@ type OutputState = { entities: Entity[]; backendEntities: Entity[]; pageInfo?: QueryEntitiesResponse['pageInfo']; + textSearch?: string; }; /** @@ -232,9 +233,17 @@ export const EntityListProvider = ( compact(Object.values(outputState.appliedFilters)), ); - if (!isEqual(previousBackendFilter, backendFilter)) { + if ( + !isEqual(previousBackendFilter, backendFilter) || + requestedFilters.text?.value !== outputState.textSearch + ) { const response = await catalogApi.queryEntities({ filter: backendFilter, + fullTextFilter: requestedFilters.text + ? { + term: requestedFilters.text.value, + } + : undefined, limit, orderFields: [{ field: 'metadata.name', order: 'asc' }], }); @@ -243,6 +252,7 @@ export const EntityListProvider = ( backendEntities: response.items, entities: response.items.filter(entityFilter), pageInfo: response.pageInfo, + textSearch: requestedFilters.text?.value, }); } } @@ -317,7 +327,7 @@ export const EntityListProvider = ( // changing filters will affect pagination, so we need to reset // the cursor and start from the first page. // TODO(vinzscam): this is currently causing issues at page reload - // where the state is not kept. Unfortunately we need to rething + // where the state is not kept. Unfortunately we need to rethink // the way filters work in order to fix this. setCursor(undefined); setRequestedFilters(prevFilters => { diff --git a/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.tsx index 771878b65c..0311cd22ca 100644 --- a/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.tsx @@ -18,6 +18,10 @@ import React from 'react'; import { Table, TableProps } from '@backstage/core-components'; import { CatalogTableRow } from './types'; +import { + EntityTextFilter, + useEntityList, +} from '@backstage/plugin-catalog-react'; type PaginatedCatalogTableProps = { prev?(): void; @@ -29,6 +33,7 @@ type PaginatedCatalogTableProps = { */ export function PaginatedCatalogTable(props: PaginatedCatalogTableProps) { const { columns, data, next, prev } = props; + const { updateFilters } = useEntityList(); return ( + updateFilters({ + text: searchText ? new EntityTextFilter(searchText) : undefined, + }) + } onPageChange={page => { if (page > 0) { next?.(); From d208a9398a740b966837c07695f043286fb44e2e Mon Sep 17 00:00:00 2001 From: Aramis Date: Mon, 8 Jan 2024 00:42:26 -0500 Subject: [PATCH 02/13] add changesets Signed-off-by: Aramis --- .changeset/clever-chicken-jog.md | 5 +++++ .changeset/long-stingrays-look.md | 5 +++++ .changeset/purple-camels-applaud.md | 5 +++++ 3 files changed, 15 insertions(+) create mode 100644 .changeset/clever-chicken-jog.md create mode 100644 .changeset/long-stingrays-look.md create mode 100644 .changeset/purple-camels-applaud.md diff --git a/.changeset/clever-chicken-jog.md b/.changeset/clever-chicken-jog.md new file mode 100644 index 0000000000..de65947105 --- /dev/null +++ b/.changeset/clever-chicken-jog.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': minor +--- + +Added support for server side text filtering to paginated entity requests. diff --git a/.changeset/long-stingrays-look.md b/.changeset/long-stingrays-look.md new file mode 100644 index 0000000000..f1df12169e --- /dev/null +++ b/.changeset/long-stingrays-look.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': minor +--- + +Updated the paginated catalog table to support server side text filtering. diff --git a/.changeset/purple-camels-applaud.md b/.changeset/purple-camels-applaud.md new file mode 100644 index 0000000000..4f6858922a --- /dev/null +++ b/.changeset/purple-camels-applaud.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Fixed a bug where `fullTextFilter` wasn't preserved correctly in the cursor. From fae8fb815d2782d15048b2da0c58ebb80897ddee Mon Sep 17 00:00:00 2001 From: Aramis Date: Mon, 8 Jan 2024 01:04:06 -0500 Subject: [PATCH 03/13] fix tests Signed-off-by: Aramis --- .../PaginatedCatalogTable.test.tsx | 50 ++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.test.tsx index dbf37c2d5c..eb1acc0cee 100644 --- a/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.test.tsx +++ b/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.test.tsx @@ -13,11 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { ReactNode } from 'react'; import { fireEvent, render } from '@testing-library/react'; import { PaginatedCatalogTable } from './PaginatedCatalogTable'; import { screen } from '@testing-library/react'; import { CatalogTableRow } from './types'; +import { + DefaultEntityFilters, + EntityListContextProps, + MockEntityListContextProvider, +} from '@backstage/plugin-catalog-react'; describe('PaginatedCatalogTable', () => { const data = new Array(100).fill(0).map((_, index) => { @@ -45,8 +50,21 @@ describe('PaginatedCatalogTable', () => { }, ]; + const wrapInContext = ( + node: ReactNode, + value?: Partial>, + ) => { + return ( + + {node} + + ); + }; + it('should display all the items', () => { - render(); + render( + wrapInContext(), + ); for (const item of data) { expect(screen.queryByText(item.resolved.name)).toBeInTheDocument(); @@ -55,7 +73,13 @@ describe('PaginatedCatalogTable', () => { it('should display and invoke the next button', async () => { const { rerender } = render( - , + wrapInContext( + , + ), ); expect( @@ -64,7 +88,11 @@ describe('PaginatedCatalogTable', () => { const fn = jest.fn(); - rerender(); + rerender( + wrapInContext( + , + ), + ); const nextButton = screen.queryAllByRole('button', { name: 'Next Page', @@ -77,7 +105,13 @@ describe('PaginatedCatalogTable', () => { it('should display and invoke the prev button', async () => { const { rerender } = render( - , + wrapInContext( + , + ), ); expect( @@ -86,7 +120,11 @@ describe('PaginatedCatalogTable', () => { const fn = jest.fn(); - rerender(); + rerender( + wrapInContext( + , + ), + ); const prevButton = screen.queryAllByRole('button', { name: 'Previous Page', From c9c59d0bf101293326d878620cfeb5d8a151226f Mon Sep 17 00:00:00 2001 From: Aramis Date: Fri, 19 Jan 2024 09:49:37 -0500 Subject: [PATCH 04/13] add test cases for using search text Signed-off-by: Aramis --- .../src/hooks/useEntityListProvider.test.tsx | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index f6881d4a80..42c56eb4c9 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -33,6 +33,7 @@ import { catalogApiRef } from '../api'; import { starredEntitiesApiRef, MockStarredEntitiesApi } from '../apis'; import { EntityKindFilter, + EntityTextFilter, EntityTypeFilter, EntityUserFilter, } from '../filters'; @@ -171,6 +172,30 @@ describe('', () => { }); }); + it('ignores search text when not paginating', async () => { + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ pagination }), + initialProps: { + userFilter: 'all', + }, + }); + + act(() => + result.current.updateFilters({ + text: new EntityTextFilter('1'), + }), + ); + + await waitFor(() => { + expect(result.current.backendEntities.length).toBe(2); + expect(result.current.entities.length).toBe(1); + expect(mockCatalogApi.getEntities).toHaveBeenCalledTimes(1); + expect(mockCatalogApi.getEntities).toHaveBeenCalledWith({ + filter: { kind: 'component' }, + }); + }); + }); + it('resolves query param filter values', async () => { const query = qs.stringify({ filters: { kind: 'component', type: 'service' }, @@ -289,6 +314,34 @@ describe('', () => { jest.clearAllMocks(); }); + it('sends search text to the backend', async () => { + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ pagination }), + initialProps: { + userFilter: 'all', + }, + }); + + act(() => + result.current.updateFilters({ + text: new EntityTextFilter('2'), + }), + ); + + await waitFor(() => { + expect(result.current.entities.length).toBe(1); + expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(1); + expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith({ + filter: { kind: 'component' }, + limit, + orderFields, + fullTextFilter: { + term: '2', + }, + }); + }); + }); + it('should send backend filters', async () => { const { result } = renderHook(() => useEntityList(), { wrapper: createWrapper({ pagination }), From 267dc8e8db1d2d0ace129a0e2e1a0075aeaf73bf Mon Sep 17 00:00:00 2001 From: Aramis Date: Thu, 25 Jan 2024 18:36:01 -0500 Subject: [PATCH 05/13] add a negative check as well Signed-off-by: Aramis --- plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index 42c56eb4c9..4ba5f51503 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -329,6 +329,7 @@ describe('', () => { ); await waitFor(() => { + expect(mockCatalogApi.getEntities).not.toHaveBeenCalledTimes(1); expect(result.current.entities.length).toBe(1); expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(1); expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith({ From 03604cffa2f5b8d39879e906ae309056fcb18dd1 Mon Sep 17 00:00:00 2001 From: Aramis Date: Fri, 26 Jan 2024 11:46:38 -0500 Subject: [PATCH 06/13] support non 'filter'-keyed filters in paginated backend requests Signed-off-by: Aramis --- plugins/catalog-react/api-report.md | 10 +++++++ .../UserListPicker/useAllEntitiesCount.ts | 6 ++-- .../UserListPicker/useOwnedEntitiesCount.ts | 21 +++++++++----- .../UserListPicker/useStarredEntitiesCount.ts | 5 ++-- plugins/catalog-react/src/filters.ts | 10 +++++++ .../src/hooks/useEntityListProvider.test.tsx | 1 + .../src/hooks/useEntityListProvider.tsx | 12 ++------ plugins/catalog-react/src/types.ts | 8 +++++ plugins/catalog-react/src/utils/filters.ts | 29 ++++++++++++++----- 9 files changed, 72 insertions(+), 30 deletions(-) diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 8c6b3a729f..2d6152fb07 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -15,6 +15,8 @@ import { Entity } from '@backstage/catalog-model'; import { IconButton } from '@material-ui/core'; import { IconComponent } from '@backstage/core-plugin-api'; import { InfoCardVariants } from '@backstage/core-components'; +import { JsonObject } from '@backstage/types'; +import { JsonValue } from '@backstage/types'; import { LinkProps } from '@backstage/core-components'; import { Observable } from '@backstage/types'; import { Overrides } from '@material-ui/core/styles/overrides'; @@ -217,6 +219,7 @@ export type EntityFilter = { string, string | symbol | (string | symbol)[] >; + getBackendRequestParameter?: () => Record; filterEntity?: (entity: Entity) => boolean; toQueryValue?: () => string | string[]; }; @@ -541,6 +544,13 @@ export class EntityTextFilter implements EntityFilter { // (undocumented) filterEntity(entity: Entity): boolean; // (undocumented) + getBackendRequestParameter(): { + fullTextFilter: { + term: string; + fields: string[]; + }; + }; + // (undocumented) readonly value: string; } diff --git a/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.ts b/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.ts index ba971b088d..dae6df55ee 100644 --- a/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.ts +++ b/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.ts @@ -30,13 +30,13 @@ export function useAllEntitiesCount() { const request = useMemo(() => { const { user, ...allFilters } = filters; const compacted = compact(Object.values(allFilters)); - const filter = reduceCatalogFilters(compacted); + const catalogFilters = reduceCatalogFilters(compacted); const newRequest: QueryEntitiesInitialRequest = { - filter, + ...catalogFilters, limit: 0, }; - if (Object.keys(filter).length === 0) { + if (Object.keys(catalogFilters).length === 0) { prevRequest.current = undefined; return prevRequest.current; } diff --git a/plugins/catalog-react/src/components/UserListPicker/useOwnedEntitiesCount.ts b/plugins/catalog-react/src/components/UserListPicker/useOwnedEntitiesCount.ts index 2f03a0b285..1fc147daf2 100644 --- a/plugins/catalog-react/src/components/UserListPicker/useOwnedEntitiesCount.ts +++ b/plugins/catalog-react/src/components/UserListPicker/useOwnedEntitiesCount.ts @@ -21,7 +21,7 @@ import useAsync from 'react-use/lib/useAsync'; import { catalogApiRef } from '../../api'; import { EntityOwnerFilter, EntityUserFilter } from '../../filters'; import { useEntityList } from '../../hooks'; -import { reduceCatalogFilters } from '../../utils'; +import { CatalogFilters, reduceCatalogFilters } from '../../utils'; import useAsyncFn from 'react-use/lib/useAsyncFn'; import useDeepCompareEffect from 'react-use/lib/useDeepCompareEffect'; @@ -38,7 +38,7 @@ export function useOwnedEntitiesCount() { ); const { user, owners, ...allFilters } = filters; - const { ['metadata.name']: metadata, ...filter } = reduceCatalogFilters( + const catalogFilters = reduceCatalogFilters( compact(Object.values(allFilters)), ); @@ -47,7 +47,7 @@ export function useOwnedEntitiesCount() { async (req: { ownershipEntityRefs: string[]; owners: EntityOwnerFilter | undefined; - filter: Record; + filter: CatalogFilters; }) => { const ownedClaims = getOwnedCountClaims( req.owners, @@ -60,9 +60,12 @@ export function useOwnedEntitiesCount() { return 0; } + const { ['metadata.name']: metadata, ...filter } = req.filter.filter; + const { totalItems } = await catalogApi.queryEntities({ + ...req.filter, filter: { - ...req.filter, + ...filter, 'relations.ownedBy': ownedClaims, }, limit: 0, @@ -75,15 +78,19 @@ export function useOwnedEntitiesCount() { useDeepCompareEffect(() => { // context contains no filter, wait - if (Object.keys(filter).length === 0) { + if (Object.keys(catalogFilters).length === 0) { return; } // ownershipEntityRefs is loading, wait if (ownershipEntityRefs === undefined) { return; } - fetchEntities({ ownershipEntityRefs, owners, filter }); - }, [ownershipEntityRefs, owners, filter]); + fetchEntities({ + ownershipEntityRefs, + owners, + filter: catalogFilters, + }); + }, [ownershipEntityRefs, owners, catalogFilters]); const loading = loadingEntityRefs || loadingEntityOwnership; diff --git a/plugins/catalog-react/src/components/UserListPicker/useStarredEntitiesCount.ts b/plugins/catalog-react/src/components/UserListPicker/useStarredEntitiesCount.ts index f7b2b101f3..66c0dd97e5 100644 --- a/plugins/catalog-react/src/components/UserListPicker/useStarredEntitiesCount.ts +++ b/plugins/catalog-react/src/components/UserListPicker/useStarredEntitiesCount.ts @@ -34,13 +34,14 @@ export function useStarredEntitiesCount() { const request = useMemo(() => { const { user, ...allFilters } = filters; const compacted = compact(Object.values(allFilters)); - const filter = reduceCatalogFilters(compacted); + const catalogFilters = reduceCatalogFilters(compacted); const facet = 'metadata.name'; const newRequest: QueryEntitiesInitialRequest = { + ...catalogFilters, filter: { - ...filter, + ...catalogFilters.filter, /** * here we are filtering entities by `name`. Given this filter, * the response might contain more entities than expected, in case multiple entities diff --git a/plugins/catalog-react/src/filters.ts b/plugins/catalog-react/src/filters.ts index 55983d124b..5f1324f6cf 100644 --- a/plugins/catalog-react/src/filters.ts +++ b/plugins/catalog-react/src/filters.ts @@ -108,6 +108,16 @@ export class EntityTextFilter implements EntityFilter { return true; } + getBackendRequestParameter() { + return { + fullTextFilter: { + term: this.value, + // Update this to be more dynamic based on table columns. + fields: ['metadata.name', 'metadata.title'], + }, + }; + } + private toUpperArray( value: Array, ): Array { diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index 4ba5f51503..d5208d4f10 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -338,6 +338,7 @@ describe('', () => { orderFields, fullTextFilter: { term: '2', + fields: ['metadata.name', 'metadata.title'], }, }); }); diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index 81bf8fb9e3..68b3fff735 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -233,17 +233,9 @@ export const EntityListProvider = ( compact(Object.values(outputState.appliedFilters)), ); - if ( - !isEqual(previousBackendFilter, backendFilter) || - requestedFilters.text?.value !== outputState.textSearch - ) { + if (!isEqual(previousBackendFilter, backendFilter)) { const response = await catalogApi.queryEntities({ - filter: backendFilter, - fullTextFilter: requestedFilters.text - ? { - term: requestedFilters.text.value, - } - : undefined, + ...backendFilter, limit, orderFields: [{ field: 'metadata.name', order: 'asc' }], }); diff --git a/plugins/catalog-react/src/types.ts b/plugins/catalog-react/src/types.ts index 41c7d75f0d..161398609d 100644 --- a/plugins/catalog-react/src/types.ts +++ b/plugins/catalog-react/src/types.ts @@ -15,6 +15,7 @@ */ import { Entity } from '@backstage/catalog-model'; +import { JsonObject, JsonValue } from '@backstage/types'; /** @public */ export type EntityFilter = { @@ -29,6 +30,13 @@ export type EntityFilter = { string | symbol | (string | symbol)[] >; + /** + * A superset of `getCatalogFilters` that supports filtering based on parameters outside of the + * catalog filter system. Examples include `fullTextFilter` or `orderFields`. + * @returns + */ + getBackendRequestParameter?: () => Record; + /** * Filter entities on the frontend after a catalog-backend request. This function will be called * with each backend-resolved entity. This is used when frontend information is required for diff --git a/plugins/catalog-react/src/utils/filters.ts b/plugins/catalog-react/src/utils/filters.ts index 580d7b0b76..ad597c08e7 100644 --- a/plugins/catalog-react/src/utils/filters.ts +++ b/plugins/catalog-react/src/utils/filters.ts @@ -26,16 +26,29 @@ import { EntityUserFilter, UserListFilter, } from '../filters'; +import { merge } from 'lodash'; -export function reduceCatalogFilters( - filters: EntityFilter[], -): Record { +export interface CatalogFilters { + [x: string]: + | string + | Record + | undefined; + filter: Record; + fullTextFilter?: { + term: string; + }; +} + +export function reduceCatalogFilters(filters: EntityFilter[]): CatalogFilters { return filters.reduce((compoundFilter, filter) => { - return { - ...compoundFilter, - ...(filter.getCatalogFilters ? filter.getCatalogFilters() : {}), - }; - }, {} as Record); + const addedFilters = {}; + if (filter.getBackendRequestParameter) { + Object.assign(addedFilters, filter.getBackendRequestParameter()); + } else if (filter.getCatalogFilters) { + Object.assign(addedFilters, { filter: filter.getCatalogFilters() }); + } + return merge(compoundFilter, addedFilters); + }, {} as CatalogFilters); } /** From e90ff3d373888b98113a248d50a3a30a7c0f1c1e Mon Sep 17 00:00:00 2001 From: Aramis Date: Fri, 26 Jan 2024 11:55:06 -0500 Subject: [PATCH 07/13] remove `textSearch` from output state Signed-off-by: Aramis --- plugins/catalog-react/src/hooks/useEntityListProvider.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index 68b3fff735..abb366a568 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -124,7 +124,6 @@ type OutputState = { entities: Entity[]; backendEntities: Entity[]; pageInfo?: QueryEntitiesResponse['pageInfo']; - textSearch?: string; }; /** @@ -244,7 +243,6 @@ export const EntityListProvider = ( backendEntities: response.items, entities: response.items.filter(entityFilter), pageInfo: response.pageInfo, - textSearch: requestedFilters.text?.value, }); } } From f845dd6f4fb72be3ada9f43615edc75cf9b59d20 Mon Sep 17 00:00:00 2001 From: Aramis Date: Sun, 28 Jan 2024 11:46:55 -0500 Subject: [PATCH 08/13] add `spec.profile.displayName` to the searched fields Signed-off-by: Aramis --- plugins/catalog-react/src/filters.ts | 2 +- .../catalog-react/src/hooks/useEntityListProvider.test.tsx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-react/src/filters.ts b/plugins/catalog-react/src/filters.ts index 5f1324f6cf..b024f05a14 100644 --- a/plugins/catalog-react/src/filters.ts +++ b/plugins/catalog-react/src/filters.ts @@ -113,7 +113,7 @@ export class EntityTextFilter implements EntityFilter { fullTextFilter: { term: this.value, // Update this to be more dynamic based on table columns. - fields: ['metadata.name', 'metadata.title'], + fields: ['metadata.name', 'metadata.title', 'spec.profile.displayName'], }, }; } diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index d5208d4f10..c2328a1aef 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -338,7 +338,11 @@ describe('', () => { orderFields, fullTextFilter: { term: '2', - fields: ['metadata.name', 'metadata.title'], + fields: [ + 'metadata.name', + 'metadata.title', + 'spec.profile.displayName', + ], }, }); }); From 020f0b71db79037b3de6ca2e173b060c2052f971 Mon Sep 17 00:00:00 2001 From: Aramis Sennyey <34432188+sennyeya@users.noreply.github.com> Date: Thu, 1 Feb 2024 12:43:02 -0500 Subject: [PATCH 09/13] Apply suggestions from code review Co-authored-by: Vincenzo Scamporlino Signed-off-by: Aramis Sennyey <34432188+sennyeya@users.noreply.github.com> --- .../src/components/UserListPicker/useAllEntitiesCount.ts | 2 +- .../src/components/UserListPicker/useOwnedEntitiesCount.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.ts b/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.ts index dae6df55ee..6916d914f7 100644 --- a/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.ts +++ b/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.ts @@ -36,7 +36,7 @@ export function useAllEntitiesCount() { limit: 0, }; - if (Object.keys(catalogFilters).length === 0) { + if (Object.keys(catalogFilters.filter).length === 0) { prevRequest.current = undefined; return prevRequest.current; } diff --git a/plugins/catalog-react/src/components/UserListPicker/useOwnedEntitiesCount.ts b/plugins/catalog-react/src/components/UserListPicker/useOwnedEntitiesCount.ts index 1fc147daf2..833eeb898f 100644 --- a/plugins/catalog-react/src/components/UserListPicker/useOwnedEntitiesCount.ts +++ b/plugins/catalog-react/src/components/UserListPicker/useOwnedEntitiesCount.ts @@ -78,7 +78,7 @@ export function useOwnedEntitiesCount() { useDeepCompareEffect(() => { // context contains no filter, wait - if (Object.keys(catalogFilters).length === 0) { + if (Object.keys(catalogFilters.filter).length === 0) { return; } // ownershipEntityRefs is loading, wait From 19efab7840ae928d9661fbcce955bc96730dc33b Mon Sep 17 00:00:00 2001 From: Aramis Date: Thu, 1 Feb 2024 15:10:51 -0500 Subject: [PATCH 10/13] remove getBackendParameter from interface Signed-off-by: Aramis --- plugins/catalog-react/src/filters.ts | 10 ++++------ plugins/catalog-react/src/types.ts | 8 -------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/plugins/catalog-react/src/filters.ts b/plugins/catalog-react/src/filters.ts index b024f05a14..cf94272280 100644 --- a/plugins/catalog-react/src/filters.ts +++ b/plugins/catalog-react/src/filters.ts @@ -108,13 +108,11 @@ export class EntityTextFilter implements EntityFilter { return true; } - getBackendRequestParameter() { + getFullTextFilter() { return { - fullTextFilter: { - term: this.value, - // Update this to be more dynamic based on table columns. - fields: ['metadata.name', 'metadata.title', 'spec.profile.displayName'], - }, + term: this.value, + // Update this to be more dynamic based on table columns. + fields: ['metadata.name', 'metadata.title', 'spec.profile.displayName'], }; } diff --git a/plugins/catalog-react/src/types.ts b/plugins/catalog-react/src/types.ts index 161398609d..41c7d75f0d 100644 --- a/plugins/catalog-react/src/types.ts +++ b/plugins/catalog-react/src/types.ts @@ -15,7 +15,6 @@ */ import { Entity } from '@backstage/catalog-model'; -import { JsonObject, JsonValue } from '@backstage/types'; /** @public */ export type EntityFilter = { @@ -30,13 +29,6 @@ export type EntityFilter = { string | symbol | (string | symbol)[] >; - /** - * A superset of `getCatalogFilters` that supports filtering based on parameters outside of the - * catalog filter system. Examples include `fullTextFilter` or `orderFields`. - * @returns - */ - getBackendRequestParameter?: () => Record; - /** * Filter entities on the frontend after a catalog-backend request. This function will be called * with each backend-resolved entity. This is used when frontend information is required for From bc42ec30b5c8feb20d5c4789cc561509ae649cfb Mon Sep 17 00:00:00 2001 From: Aramis Date: Thu, 1 Feb 2024 15:14:23 -0500 Subject: [PATCH 11/13] update usage of new getFullTextFields Signed-off-by: Aramis --- plugins/catalog-react/src/filters.ts | 2 +- plugins/catalog-react/src/utils/filters.ts | 30 ++++++++++++---------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/plugins/catalog-react/src/filters.ts b/plugins/catalog-react/src/filters.ts index cf94272280..dda13338a7 100644 --- a/plugins/catalog-react/src/filters.ts +++ b/plugins/catalog-react/src/filters.ts @@ -108,7 +108,7 @@ export class EntityTextFilter implements EntityFilter { return true; } - getFullTextFilter() { + getFullTextFilters() { return { term: this.value, // Update this to be more dynamic based on table columns. diff --git a/plugins/catalog-react/src/utils/filters.ts b/plugins/catalog-react/src/utils/filters.ts index ad597c08e7..82d7358b91 100644 --- a/plugins/catalog-react/src/utils/filters.ts +++ b/plugins/catalog-react/src/utils/filters.ts @@ -26,29 +26,31 @@ import { EntityUserFilter, UserListFilter, } from '../filters'; -import { merge } from 'lodash'; export interface CatalogFilters { - [x: string]: - | string - | Record - | undefined; filter: Record; fullTextFilter?: { term: string; }; } +function isEntityTextFilter(t: EntityFilter): t is EntityTextFilter { + return !!(t as EntityTextFilter).getFullTextFilters; +} + export function reduceCatalogFilters(filters: EntityFilter[]): CatalogFilters { - return filters.reduce((compoundFilter, filter) => { - const addedFilters = {}; - if (filter.getBackendRequestParameter) { - Object.assign(addedFilters, filter.getBackendRequestParameter()); - } else if (filter.getCatalogFilters) { - Object.assign(addedFilters, { filter: filter.getCatalogFilters() }); - } - return merge(compoundFilter, addedFilters); - }, {} as CatalogFilters); + const condensedFilters = filters.reduce( + (compoundFilter, filter) => { + return { + ...compoundFilter, + ...(filter.getCatalogFilters ? filter.getCatalogFilters() : {}), + }; + }, + {}, + ); + + const fullTextFilter = filters.find(isEntityTextFilter)?.getFullTextFilters(); + return { filter: condensedFilters, fullTextFilter }; } /** From 46c60499ce3b7fa472ff8a39eb3e2dc69c60e9da Mon Sep 17 00:00:00 2001 From: Aramis Date: Thu, 1 Feb 2024 15:16:50 -0500 Subject: [PATCH 12/13] fix api report Signed-off-by: Aramis --- plugins/catalog-react/api-report.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 2d6152fb07..62bdf3f2c9 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -15,8 +15,6 @@ import { Entity } from '@backstage/catalog-model'; import { IconButton } from '@material-ui/core'; import { IconComponent } from '@backstage/core-plugin-api'; import { InfoCardVariants } from '@backstage/core-components'; -import { JsonObject } from '@backstage/types'; -import { JsonValue } from '@backstage/types'; import { LinkProps } from '@backstage/core-components'; import { Observable } from '@backstage/types'; import { Overrides } from '@material-ui/core/styles/overrides'; @@ -219,7 +217,6 @@ export type EntityFilter = { string, string | symbol | (string | symbol)[] >; - getBackendRequestParameter?: () => Record; filterEntity?: (entity: Entity) => boolean; toQueryValue?: () => string | string[]; }; @@ -544,11 +541,9 @@ export class EntityTextFilter implements EntityFilter { // (undocumented) filterEntity(entity: Entity): boolean; // (undocumented) - getBackendRequestParameter(): { - fullTextFilter: { - term: string; - fields: string[]; - }; + getFullTextFilters(): { + term: string; + fields: string[]; }; // (undocumented) readonly value: string; From f6a030a647325fd684b07efd06b570919e40c9ce Mon Sep 17 00:00:00 2001 From: Aramis Date: Sat, 3 Feb 2024 16:27:29 -0500 Subject: [PATCH 13/13] remove pagination from public app Signed-off-by: Aramis --- packages/app/src/App.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 2ea0fefdea..d2dde95f68 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -177,10 +177,7 @@ const routes = ( }> {homePage} - } - /> + } /> }