diff --git a/.changeset/modern-buses-protect.md b/.changeset/modern-buses-protect.md
new file mode 100644
index 0000000000..2aed52eca0
--- /dev/null
+++ b/.changeset/modern-buses-protect.md
@@ -0,0 +1,7 @@
+---
+'@backstage/plugin-catalog-react': patch
+---
+
+When a user has zero owned entities when viewing an entity kind in the catalog
+page, it will be automatically redirected to see all the entities. Furthermore,
+for the kind User and Group there are no longer the owned selector.
diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx
index f861355762..9aafdf1437 100644
--- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx
+++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx
@@ -122,26 +122,26 @@ export const UserListPicker = ({
const classes = useStyles();
const configApi = useApi(configApiRef);
const orgName = configApi.getOptionalString('organization.name') ?? 'Company';
+ const { filters, updateFilters, backendEntities, queryParameters } =
+ useEntityListProvider();
// Remove group items that aren't in availableFilters and exclude
// any now-empty groups.
+ const userAndGroupFilterIds = ['starred', 'all'];
const filterGroups = getFilterGroups(orgName)
.map(filterGroup => ({
...filterGroup,
- items: filterGroup.items.filter(
- ({ id }) => !availableFilters || availableFilters.includes(id),
+ items: filterGroup.items.filter(({ id }) =>
+ // TODO: avoid hardcoding kinds here
+ ['group', 'user'].some(kind => kind === queryParameters.kind)
+ ? userAndGroupFilterIds.includes(id)
+ : !availableFilters || availableFilters.includes(id),
),
}))
.filter(({ items }) => !!items.length);
- const { filters, updateFilters, backendEntities, queryParameters } =
- useEntityListProvider();
-
const { isStarredEntity } = useStarredEntities();
const { isOwnedEntity } = useEntityOwnership();
- const [selectedUserFilter, setSelectedUserFilter] = useState(
- [queryParameters.user].flat()[0] ?? initialFilter,
- );
// Static filters; used for generating counts of potentially unselected kinds
const ownedFilter = useMemo(
@@ -153,6 +153,19 @@ export const UserListPicker = ({
[isOwnedEntity, isStarredEntity],
);
+ // To show proper counts for each section, apply all other frontend filters _except_ the user
+ // filter that's controlled by this picker.
+ const [entitiesWithoutUserFilter, setEntitiesWithoutUserFilter] =
+ useState(backendEntities);
+ const totalOwnedUserEntities = entitiesWithoutUserFilter.filter(entity =>
+ ownedFilter.filterEntity(entity),
+ ).length;
+ const [selectedUserFilter, setSelectedUserFilter] = useState(
+ totalOwnedUserEntities > 0
+ ? [queryParameters.user].flat()[0] ?? initialFilter
+ : 'all',
+ );
+
useEffect(() => {
updateFilters({
user: selectedUserFilter
@@ -165,10 +178,6 @@ export const UserListPicker = ({
});
}, [selectedUserFilter, isOwnedEntity, isStarredEntity, updateFilters]);
- // To show proper counts for each section, apply all other frontend filters _except_ the user
- // filter that's controlled by this picker.
- const [entitiesWithoutUserFilter, setEntitiesWithoutUserFilter] =
- useState(backendEntities);
useEffect(() => {
const filterFn = reduceEntityFilters(
compact(Object.values({ ...filters, user: undefined })),
@@ -179,9 +188,7 @@ export const UserListPicker = ({
function getFilterCount(id: UserListFilterKind) {
switch (id) {
case 'owned':
- return entitiesWithoutUserFilter.filter(entity =>
- ownedFilter.filterEntity(entity),
- ).length;
+ return totalOwnedUserEntities;
case 'starred':
return entitiesWithoutUserFilter.filter(entity =>
starredFilter.filterEntity(entity),
diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx
index b96f63304a..fd5cf3b9dd 100644
--- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx
+++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx
@@ -137,12 +137,27 @@ describe('', () => {
const { result, waitFor } = renderHook(() => useEntityListProvider(), {
wrapper,
initialProps: {
- userFilter: 'owned',
+ userFilter: 'all',
},
});
await waitFor(() => !!result.current.entities.length);
expect(result.current.backendEntities.length).toBe(2);
- expect(result.current.entities.length).toBe(1);
+
+ act(() =>
+ result.current.updateFilters({
+ user: new UserListFilter(
+ 'owned',
+ entity => entity.metadata.name === 'component-1',
+ () => true,
+ ),
+ }),
+ );
+
+ await waitFor(() => {
+ expect(result.current.backendEntities.length).toBe(2);
+ expect(result.current.entities.length).toBe(1);
+ expect(mockCatalogApi.getEntities).toHaveBeenCalledTimes(1);
+ });
});
it('resolves query param filter values', async () => {
diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx
index 0a46580c12..effd9f5bc6 100644
--- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx
+++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx
@@ -189,12 +189,14 @@ describe('CatalogPage', () => {
c => c.tagName === 'SPAN',
);
const columnHeaderLabels = columnHeader.map(c => c.textContent);
-
expect(columnHeaderLabels).toEqual(['Foo', 'Bar', 'Baz', 'Actions']);
}, 20_000);
it('should render the default actions of an item in the grid', async () => {
- const { findByTitle, findByText } = await renderWrapped();
+ const { getByTestId, findByTitle, findByText } = await renderWrapped(
+ ,
+ );
+ fireEvent.click(getByTestId('user-picker-owned'));
expect(await findByText(/Owned \(1\)/)).toBeInTheDocument();
expect(await findByTitle(/View/)).toBeInTheDocument();
expect(await findByTitle(/Edit/)).toBeInTheDocument();
@@ -221,9 +223,10 @@ describe('CatalogPage', () => {
},
];
- const { findByTitle, findByText } = await renderWrapped(
+ const { getByTestId, findByTitle, findByText } = await renderWrapped(
,
);
+ fireEvent.click(getByTestId('user-picker-owned'));
expect(await findByText(/Owned \(1\)/)).toBeInTheDocument();
expect(await findByTitle(/Foo Action/)).toBeInTheDocument();
expect(await findByTitle(/Bar Action/)).toBeInTheDocument();
@@ -235,6 +238,7 @@ describe('CatalogPage', () => {
// https://github.com/mbrn/material-table/issues/1293
it('should render', async () => {
const { findByText, getByTestId } = await renderWrapped();
+ fireEvent.click(getByTestId('user-picker-owned'));
await expect(findByText(/Owned \(1\)/)).resolves.toBeInTheDocument();
fireEvent.click(getByTestId('user-picker-all'));
await expect(findByText(/All \(2\)/)).resolves.toBeInTheDocument();
@@ -250,7 +254,8 @@ describe('CatalogPage', () => {
// this test is for fixing the bug after favoriting an entity, the matching
// entities defaulting to "owned" filter and not based on the selected filter
it('should render the correct entities filtered on the selected filter', async () => {
- await renderWrapped();
+ const { getByTestId } = await renderWrapped();
+ fireEvent.click(getByTestId('user-picker-owned'));
await expect(screen.findByText(/Owned \(1\)/)).resolves.toBeInTheDocument();
fireEvent.click(screen.getByTestId('user-picker-starred'));
await expect(