Merge pull request #28810 from StateFarmIns/add-additional-props-to-entity-pickers

feat: add additional props to entity pickers
This commit is contained in:
Vincenzo Scamporlino
2025-02-19 14:15:41 +01:00
committed by GitHub
8 changed files with 131 additions and 6 deletions
+7
View File
@@ -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.
+6 -1
View File
@@ -208,6 +208,7 @@ export type EntityAutocompletePickerProps<
InputProps?: TextFieldProps;
initialSelectedOptions?: string[];
filtersForAvailableValues?: Array<keyof T>;
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)
@@ -393,4 +393,22 @@ describe('<EntityAutocompletePicker/>', () => {
}),
);
});
it("doesn't render when hidden", async () => {
const catalogApi = makeMockCatalogApi();
render(
<TestApiProvider apis={[[catalogApiRef, catalogApi]]}>
<MockEntityListContextProvider value={{}}>
<EntityAutocompletePicker<EntityFilters>
label="Options"
path="spec.options"
name="options"
Filter={EntityOptionFilter}
hidden
/>
</MockEntityListContextProvider>
</TestApiProvider>,
);
await waitFor(() => expect(screen.queryByText('Options')).toBeNull());
});
});
@@ -52,6 +52,7 @@ export type EntityAutocompletePickerProps<
InputProps?: TextFieldProps;
initialSelectedOptions?: string[];
filtersForAvailableValues?: Array<keyof T>;
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 : (
<Box className={classes.root} pb={1} pt={1}>
<CatalogAutocomplete<string, true>
multiple
@@ -260,4 +260,36 @@ describe('<EntityTagPicker/>', () => {
}),
);
});
it('respects the initial filter value', async () => {
const updateFilters = jest.fn();
await renderInTestApp(
<TestApiProvider apis={[[catalogApiRef, catalogApi]]}>
<MockEntityListContextProvider
value={{
updateFilters,
}}
>
<EntityTagPicker initialFilter={['tag3']} />
</MockEntityListContextProvider>
</TestApiProvider>,
);
await waitFor(() =>
expect(updateFilters).toHaveBeenLastCalledWith({
tags: new EntityTagFilter(['tag3']),
}),
);
});
it("doesn't render when hidden", async () => {
await renderInTestApp(
<TestApiProvider apis={[[catalogApiRef, catalogApi]]}>
<MockEntityListContextProvider value={{}}>
<EntityTagPicker hidden />
</MockEntityListContextProvider>
</TestApiProvider>,
);
await waitFor(() => expect(screen.queryByText('Tags')).toBeNull());
});
});
@@ -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}
/>
);
};
@@ -547,6 +547,35 @@ describe('<UserListPicker />', () => {
});
});
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(
<Picker initialFilter="starred" alwaysKeepFilters />,
);
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('<UserListPicker />', () => {
}),
);
});
it("doesn't render when hidden", async () => {
await renderInTestApp(
<ApiProvider apis={apis}>
<MockEntityListContextProvider value={{}}>
<UserListPicker hidden />
</MockEntityListContextProvider>
</ApiProvider>,
);
await waitFor(() =>
expect(mockIdentityApi.getBackstageIdentity).toHaveBeenCalled(),
);
await waitFor(() =>
expect(mockCatalogApi.queryEntities).toHaveBeenCalled(),
);
expect(screen.queryByText('Personal')).toBeNull();
expect(screen.queryByText('Test Company')).toBeNull();
});
});
});
});
@@ -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 : (
<Card className={classes.root}>
{filterGroups.map(group => (
<Fragment key={group.name}>