Merge pull request #28825 from annervisser/fix-update-entityautocompletepicker-selected-options-when-value-changes

This commit is contained in:
Ben Lambert
2025-02-26 18:06:51 +01:00
committed by GitHub
3 changed files with 54 additions and 32 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-react': patch
---
update EntityAutocompletePicker selected options when filter value is changed externally
@@ -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('<EntityAutocompletePicker/>', () => {
});
});
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<EntityFilters>();
return (
<button
data-testid="external-filter-change-button"
onClick={() =>
updateFilters({ options: new EntityOptionFilter(['option3']) })
}
>
Trigger external filter change
</button>
);
};
render(
<TestApiProvider apis={[[catalogApiRef, mockCatalogApi]]}>
<MockEntityListContextProvider<EntityFilters>
value={{
updateFilters,
queryParameters: { options: ['option1'] },
filters: { options: new EntityOptionFilter(['option2']) },
}}
>
<EntityAutocompletePicker<EntityFilters>
@@ -298,34 +311,21 @@ describe('<EntityAutocompletePicker/>', () => {
name="options"
Filter={EntityOptionFilter}
/>
<ChangeFilterButton />
</MockEntityListContextProvider>
</TestApiProvider>,
);
await waitFor(() =>
expect(updateFilters).toHaveBeenLastCalledWith({
options: new EntityOptionFilter(['option1']),
}),
expect(screen.queryByText('Options')).toBeInTheDocument(),
);
rendered.rerender(
<TestApiProvider apis={[[catalogApiRef, mockCatalogApi]]}>
<MockEntityListContextProvider<EntityFilters>
value={{
updateFilters,
queryParameters: { options: ['option2'] },
}}
>
<EntityAutocompletePicker<EntityFilters>
label="Options"
path="spec.options"
name="options"
Filter={EntityOptionFilter}
/>
</MockEntityListContextProvider>
</TestApiProvider>,
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 () => {
@@ -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<T extends DefaultEntityFilters> = {
@@ -116,11 +117,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
@@ -134,12 +137,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<T>);
}, [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)) ||