diff --git a/.changeset/ninety-experts-cheat.md b/.changeset/ninety-experts-cheat.md new file mode 100644 index 0000000000..093145ecc6 --- /dev/null +++ b/.changeset/ninety-experts-cheat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +update EntityAutocompletePicker selected options when filter value is changed externally diff --git a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx index f025c14011..5fd8b549ce 100644 --- a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx @@ -14,16 +14,16 @@ * limitations under the License. */ -import { fireEvent, render, waitFor, screen } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import React from 'react'; import { - MockEntityListContextProvider, catalogApiMock, + MockEntityListContextProvider, } from '@backstage/plugin-catalog-react/testUtils'; import { EntityAutocompletePicker } from './EntityAutocompletePicker'; import { TestApiProvider } from '@backstage/test-utils'; import { catalogApiRef } from '../../api'; -import { DefaultEntityFilters } from '../../hooks'; +import { DefaultEntityFilters, useEntityList } from '../../hooks'; import { Entity } from '@backstage/catalog-model'; import { EntityFilter } from '../../types'; import { EntityKindFilter, EntityTypeFilter } from '../../filters'; @@ -281,15 +281,28 @@ describe('', () => { }); }); - it('responds to external queryParameters changes', async () => { + it('responds to external filter changes', async () => { const mockCatalogApi = makeMockCatalogApi(); - const updateFilters = jest.fn(); - const rendered = render( + const ChangeFilterButton = () => { + const { updateFilters } = useEntityList(); + + return ( + + ); + }; + + render( value={{ - updateFilters, - queryParameters: { options: ['option1'] }, + filters: { options: new EntityOptionFilter(['option2']) }, }} > @@ -298,34 +311,21 @@ describe('', () => { name="options" Filter={EntityOptionFilter} /> + , ); + await waitFor(() => - expect(updateFilters).toHaveBeenLastCalledWith({ - options: new EntityOptionFilter(['option1']), - }), + expect(screen.queryByText('Options')).toBeInTheDocument(), ); - rendered.rerender( - - - value={{ - updateFilters, - queryParameters: { options: ['option2'] }, - }} - > - - label="Options" - path="spec.options" - name="options" - Filter={EntityOptionFilter} - /> - - , + expect(screen.queryByText('option2')).toBeInTheDocument(); + + screen.getByTestId('external-filter-change-button').click(); + await waitFor(() => + expect(screen.queryByText('option3')).toBeInTheDocument(), ); - expect(updateFilters).toHaveBeenLastCalledWith({ - options: new EntityOptionFilter(['option2']), - }); + expect(screen.queryByText('option2')).not.toBeInTheDocument(); }); it('filters available values by kind as default', async () => { diff --git a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx index 09b5c1e908..c134446d27 100644 --- a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx +++ b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx @@ -29,6 +29,7 @@ import { import { EntityFilter } from '../../types'; import { reduceBackendCatalogFilters } from '../../utils/filters'; import { CatalogAutocomplete } from '../CatalogAutocomplete'; +import { isEqual } from 'lodash'; /** @public */ export type AllowedEntityFilters = { @@ -114,11 +115,13 @@ export function EntityAutocompletePicker< [queryParameter], ); + const filteredOptions = (filters[name] as unknown as { values: string[] }) + ?.values; + const [selectedOptions, setSelectedOptions] = useState( queryParameters.length ? queryParameters - : (filters[name] as unknown as { values: string[] })?.values ?? - initialSelectedOptions, + : filteredOptions ?? initialSelectedOptions, ); // Set selected options on query parameter updates; this happens at initial page load and from @@ -132,12 +135,26 @@ export function EntityAutocompletePicker< const availableOptions = Object.keys(availableValues ?? {}); const shouldAddFilter = selectedOptions.length && availableOptions.length; + // Update filter value when selectedOptions change useEffect(() => { updateFilters({ [name]: shouldAddFilter ? new Filter(selectedOptions) : undefined, } as Partial); }, [name, shouldAddFilter, selectedOptions, Filter, updateFilters]); + // Update selected options when filter value changes + useEffect(() => { + if (!shouldAddFilter) return; + + const newSelectedOptions = filteredOptions ?? []; + + // Check value is actually different (not just a different reference) to prevent selectedOptions <> filters loop + if (!isEqual(newSelectedOptions, selectedOptions)) { + setSelectedOptions(newSelectedOptions); + } + // eslint-disable-next-line react-hooks/exhaustive-deps -- Don't re-set filter value when selectedOptions changes + }, [filteredOptions]); + const filter = filters[name]; if ( (filter && typeof filter === 'object' && !('values' in filter)) ||