diff --git a/.changeset/lazy-pumas-sniff.md b/.changeset/lazy-pumas-sniff.md new file mode 100644 index 0000000000..997c318b30 --- /dev/null +++ b/.changeset/lazy-pumas-sniff.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Adds an argument for which filters should be applied when fetching/counting available values diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 39db26df74..20831da43f 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -204,6 +204,7 @@ export type EntityAutocompletePickerProps< }; InputProps?: TextFieldProps; initialSelectedOptions?: string[]; + filtersForAvailableValues?: Array; }; // @public diff --git a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx index 32e66dc578..94af52f374 100644 --- a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx @@ -24,6 +24,7 @@ import { CatalogApi } from '@backstage/catalog-client'; import { DefaultEntityFilters } from '../../hooks'; import { Entity } from '@backstage/catalog-model'; import { EntityFilter } from '../../types'; +import { EntityKindFilter, EntityTypeFilter } from '../../filters'; interface EntityFilters extends DefaultEntityFilters { options?: EntityOptionFilter; @@ -46,13 +47,17 @@ class EntityOptionFilter implements EntityFilter { } describe('', () => { - const mockCatalogApi = { - getEntityFacets: async () => ({ + const mockCatalogApi: Partial> = { + getEntityFacets: jest.fn().mockResolvedValue({ facets: { 'spec.options': options.map((value, idx) => ({ value, count: idx })), }, }), - } as unknown as CatalogApi; + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); it('renders all options', async () => { render( @@ -71,6 +76,12 @@ describe('', () => { expect(screen.getByText('Options')).toBeInTheDocument(), ); + // should have called catalog backend without any filters applied + expect(mockCatalogApi.getEntityFacets).toHaveBeenCalledWith({ + facets: ['spec.options'], + filter: {}, + }); + fireEvent.click(screen.getByTestId('options-picker-expand')); options.forEach(option => { expect(screen.getByText(option)).toBeInTheDocument(); @@ -267,4 +278,68 @@ describe('', () => { options: new EntityOptionFilter(['option2']), }); }); + + it('filters available values by kind as default', async () => { + render( + + + + label="Options" + path="spec.options" + name="options" + Filter={EntityOptionFilter} + /> + + , + ); + + await waitFor(() => + expect(mockCatalogApi.getEntityFacets).toHaveBeenCalledWith({ + facets: ['spec.options'], + filter: { + kind: 'Component', + }, + }), + ); + }); + + it('can be supplied with filters for available values', async () => { + render( + + + + label="Options" + path="spec.options" + name="options" + Filter={EntityOptionFilter} + filtersForAvailableValues={['kind', 'type']} + /> + + , + ); + + await waitFor(() => + expect(mockCatalogApi.getEntityFacets).toHaveBeenCalledWith({ + facets: ['spec.options'], + filter: { + kind: 'Component', + 'spec.type': ['service'], + }, + }), + ); + }); }); diff --git a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx index 1b7539b912..fe3296a8a0 100644 --- a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx +++ b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx @@ -28,6 +28,7 @@ import { useEntityList, } from '../../hooks/useEntityListProvider'; import { EntityFilter } from '../../types'; +import { reduceBackendCatalogFilters } from '../../utils'; /** @public */ export type AllowedEntityFilters = { @@ -50,6 +51,7 @@ export type EntityAutocompletePickerProps< Filter: { new (values: string[]): NonNullable }; InputProps?: TextFieldProps; initialSelectedOptions?: string[]; + filtersForAvailableValues?: Array; }; /** @public */ @@ -76,6 +78,7 @@ export function EntityAutocompletePicker< Filter, InputProps, initialSelectedOptions = [], + filtersForAvailableValues = ['kind'], } = props; const classes = useStyles(); @@ -87,17 +90,22 @@ export function EntityAutocompletePicker< } = useEntityList(); const catalogApi = useApi(catalogApiRef); + const availableValuesFilters = filtersForAvailableValues.map( + f => filters[f] as EntityFilter | undefined, + ); const { value: availableValues } = useAsync(async () => { const facet = path; const { facets } = await catalogApi.getEntityFacets({ facets: [facet], - filter: filters.kind?.getCatalogFilters(), + filter: reduceBackendCatalogFilters( + availableValuesFilters.filter(Boolean) as EntityFilter[], + ), }); return Object.fromEntries( facets[facet].map(({ value, count }) => [value, count]), ); - }, [filters.kind]); + }, [...availableValuesFilters]); const queryParameters = useMemo( () => [queryParameter].flat().filter(Boolean) as string[],