diff --git a/.changeset/olive-planes-pay.md b/.changeset/olive-planes-pay.md new file mode 100644 index 0000000000..1997ce1c71 --- /dev/null +++ b/.changeset/olive-planes-pay.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-react': minor +--- + +Added `hidden` prop to `EntityTagPicker`, `EntityAutocompletePicker` and `UserListPicker`. +Added `initialFilter` prop to `EntityTagPicker` to set an initial filter for the picker. +Added `alwaysKeepFilters` prop to `UserListPicker` to prevent filters from resetting when no entities match the initial filters. diff --git a/plugins/catalog-react/report.api.md b/plugins/catalog-react/report.api.md index b3495faf08..774898b47d 100644 --- a/plugins/catalog-react/report.api.md +++ b/plugins/catalog-react/report.api.md @@ -208,6 +208,7 @@ export type EntityAutocompletePickerProps< InputProps?: TextFieldProps; initialSelectedOptions?: string[]; filtersForAvailableValues?: Array; + hidden?: boolean; }; // @public @@ -580,6 +581,8 @@ export const EntityTagPicker: ( // @public (undocumented) export type EntityTagPickerProps = { showCounts?: boolean; + initialFilter?: string[]; + hidden?: boolean; }; // @public @@ -818,12 +821,14 @@ export type UserListFilterKind = 'owned' | 'starred' | 'all'; // @public (undocumented) export const UserListPicker: ( props: UserListPickerProps, -) => React_2.JSX.Element; +) => React_2.JSX.Element | null; // @public (undocumented) export type UserListPickerProps = { initialFilter?: UserListFilterKind; availableFilters?: UserListFilterKind[]; + hidden?: boolean; + alwaysKeepFilters?: boolean; }; // @public (undocumented) diff --git a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx index f025c14011..5a8fae986f 100644 --- a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx @@ -393,4 +393,22 @@ describe('', () => { }), ); }); + + it("doesn't render when hidden", async () => { + const catalogApi = makeMockCatalogApi(); + render( + + + + label="Options" + path="spec.options" + name="options" + Filter={EntityOptionFilter} + hidden + /> + + , + ); + await waitFor(() => expect(screen.queryByText('Options')).toBeNull()); + }); }); diff --git a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx index 09b5c1e908..4fb9fee07a 100644 --- a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx +++ b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx @@ -52,6 +52,7 @@ export type EntityAutocompletePickerProps< InputProps?: TextFieldProps; initialSelectedOptions?: string[]; filtersForAvailableValues?: Array; + hidden?: boolean; }; /** @public */ @@ -82,6 +83,7 @@ export function EntityAutocompletePicker< InputProps, initialSelectedOptions = [], filtersForAvailableValues = ['kind'], + hidden, } = props; const classes = useStyles(); @@ -146,7 +148,7 @@ export function EntityAutocompletePicker< return null; } - return ( + return hidden ? null : ( multiple diff --git a/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.test.tsx b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.test.tsx index ca0c242b8b..81000d4fcf 100644 --- a/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.test.tsx @@ -260,4 +260,36 @@ describe('', () => { }), ); }); + + it('respects the initial filter value', async () => { + const updateFilters = jest.fn(); + await renderInTestApp( + + + + + , + ); + + await waitFor(() => + expect(updateFilters).toHaveBeenLastCalledWith({ + tags: new EntityTagFilter(['tag3']), + }), + ); + }); + + it("doesn't render when hidden", async () => { + await renderInTestApp( + + + + , + ); + await waitFor(() => expect(screen.queryByText('Tags')).toBeNull()); + }); }); diff --git a/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx index 2ab3622474..06b8b06e6f 100644 --- a/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx +++ b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx @@ -27,6 +27,8 @@ export type CatalogReactEntityTagPickerClassKey = 'input'; /** @public */ export type EntityTagPickerProps = { showCounts?: boolean; + initialFilter?: string[]; + hidden?: boolean; }; const useStyles = makeStyles( @@ -47,6 +49,8 @@ export const EntityTagPicker = (props: EntityTagPickerProps) => { Filter={EntityTagFilter} showCounts={props.showCounts} InputProps={{ className: classes.input }} + initialSelectedOptions={props.initialFilter ? props.initialFilter : []} + hidden={props.hidden} /> ); }; diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx index 92b51786d0..2471ce5277 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx @@ -547,6 +547,35 @@ describe('', () => { }); }); + it('doesn\nt reset the filter to "all" when entities are loaded and alwaysKeepFilters is set to true', async () => { + mockCatalogApi.queryEntities?.mockImplementation(async request => { + if ( + ( + (request as QueryEntitiesInitialRequest).filter as Record< + string, + string + > + )['metadata.name'] + ) { + return { items: [], totalItems: 0, pageInfo: {} }; + } + return mockQueryEntitiesImplementation(request); + }); + + await renderInTestApp( + , + ); + + await waitFor(() => + expect(updateFilters).toHaveBeenLastCalledWith({ + user: EntityUserFilter.starred([ + 'component:default/e-1', + 'component:default/e-2', + ]), + }), + ); + }); + describe(`when there are some owned entities present`, () => { it('does not reset the filter while entities are loading', async () => { mockCatalogApi.queryEntities?.mockImplementation(request => { @@ -645,6 +674,25 @@ describe('', () => { }), ); }); + + it("doesn't render when hidden", async () => { + await renderInTestApp( + + + + , + ); + + await waitFor(() => + expect(mockIdentityApi.getBackstageIdentity).toHaveBeenCalled(), + ); + await waitFor(() => + expect(mockCatalogApi.queryEntities).toHaveBeenCalled(), + ); + expect(screen.queryByText('Personal')).toBeNull(); + expect(screen.queryByText('Test Company')).toBeNull(); + }); }); }); }); diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx index ce61229891..c97756dc1d 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx @@ -122,11 +122,13 @@ function getFilterGroups( export type UserListPickerProps = { initialFilter?: UserListFilterKind; availableFilters?: UserListFilterKind[]; + hidden?: boolean; + alwaysKeepFilters?: boolean; }; /** @public */ export const UserListPicker = (props: UserListPickerProps) => { - const { initialFilter, availableFilters } = props; + const { initialFilter, availableFilters, hidden, alwaysKeepFilters } = props; const classes = useStyles(); const configApi = useApi(configApiRef); const { t } = useTranslationRef(catalogReactTranslationRef); @@ -198,11 +200,18 @@ export const UserListPicker = (props: UserListPickerProps) => { !loading && !!selectedUserFilter && selectedUserFilter !== 'all' && - filterCounts[selectedUserFilter] === 0 + filterCounts[selectedUserFilter] === 0 && + !alwaysKeepFilters ) { setSelectedUserFilter('all'); } - }, [loading, filterCounts, selectedUserFilter, setSelectedUserFilter]); + }, [ + loading, + filterCounts, + selectedUserFilter, + setSelectedUserFilter, + alwaysKeepFilters, + ]); useEffect(() => { if (!selectedUserFilter) { @@ -232,7 +241,7 @@ export const UserListPicker = (props: UserListPickerProps) => { loading, ]); - return ( + return hidden ? null : ( {filterGroups.map(group => (