Merge pull request #22570 from pativa/autocomplete-picker-filters

Autocomplete picker: add argument for which filters should be applied
This commit is contained in:
Ben Lambert
2024-03-05 12:37:55 +01:00
committed by GitHub
4 changed files with 94 additions and 5 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-react': patch
---
Adds an argument for which filters should be applied when fetching/counting available values
+1
View File
@@ -204,6 +204,7 @@ export type EntityAutocompletePickerProps<
};
InputProps?: TextFieldProps;
initialSelectedOptions?: string[];
filtersForAvailableValues?: Array<keyof T>;
};
// @public
@@ -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('<EntityAutocompletePicker/>', () => {
const mockCatalogApi = {
getEntityFacets: async () => ({
const mockCatalogApi: Partial<jest.Mocked<CatalogApi>> = {
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('<EntityAutocompletePicker/>', () => {
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('<EntityAutocompletePicker/>', () => {
options: new EntityOptionFilter(['option2']),
});
});
it('filters available values by kind as default', async () => {
render(
<TestApiProvider apis={[[catalogApiRef, mockCatalogApi]]}>
<MockEntityListContextProvider
value={{
filters: {
kind: new EntityKindFilter('Component'),
type: new EntityTypeFilter(['service']),
},
}}
>
<EntityAutocompletePicker<EntityFilters>
label="Options"
path="spec.options"
name="options"
Filter={EntityOptionFilter}
/>
</MockEntityListContextProvider>
</TestApiProvider>,
);
await waitFor(() =>
expect(mockCatalogApi.getEntityFacets).toHaveBeenCalledWith({
facets: ['spec.options'],
filter: {
kind: 'Component',
},
}),
);
});
it('can be supplied with filters for available values', async () => {
render(
<TestApiProvider apis={[[catalogApiRef, mockCatalogApi]]}>
<MockEntityListContextProvider
value={{
filters: {
kind: new EntityKindFilter('Component'),
type: new EntityTypeFilter(['service']),
},
}}
>
<EntityAutocompletePicker<EntityFilters>
label="Options"
path="spec.options"
name="options"
Filter={EntityOptionFilter}
filtersForAvailableValues={['kind', 'type']}
/>
</MockEntityListContextProvider>
</TestApiProvider>,
);
await waitFor(() =>
expect(mockCatalogApi.getEntityFacets).toHaveBeenCalledWith({
facets: ['spec.options'],
filter: {
kind: 'Component',
'spec.type': ['service'],
},
}),
);
});
});
@@ -28,6 +28,7 @@ import {
useEntityList,
} from '../../hooks/useEntityListProvider';
import { EntityFilter } from '../../types';
import { reduceBackendCatalogFilters } from '../../utils';
/** @public */
export type AllowedEntityFilters<T extends DefaultEntityFilters> = {
@@ -50,6 +51,7 @@ export type EntityAutocompletePickerProps<
Filter: { new (values: string[]): NonNullable<T[Name]> };
InputProps?: TextFieldProps;
initialSelectedOptions?: string[];
filtersForAvailableValues?: Array<keyof T>;
};
/** @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<T>();
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[],