diff --git a/plugins/org/src/components/MyGroupsSidebarItem/MyGroupsSidebarItem.test.tsx b/plugins/org/src/components/MyGroupsSidebarItem/MyGroupsSidebarItem.test.tsx index 9521bcc0a2..c870d58cc1 100644 --- a/plugins/org/src/components/MyGroupsSidebarItem/MyGroupsSidebarItem.test.tsx +++ b/plugins/org/src/components/MyGroupsSidebarItem/MyGroupsSidebarItem.test.tsx @@ -24,8 +24,15 @@ import { MyGroupsSidebarItem } from './MyGroupsSidebarItem'; import GroupIcon from '@material-ui/icons/People'; import { identityApiRef } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; -import { catalogApiRef, entityRouteRef } from '@backstage/plugin-catalog-react'; +import { + catalogApiRef, + entityPresentationApiRef, + entityRouteRef, +} from '@backstage/plugin-catalog-react'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; +import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog'; +import userEvent from '@testing-library/user-event'; +import { screen } from '@testing-library/react'; describe('MyGroupsSidebarItem Test', () => { describe('For guests or users with no groups', () => { @@ -40,6 +47,10 @@ describe('MyGroupsSidebarItem Test', () => { apis={[ [identityApiRef, identityApi], [catalogApiRef, catalogApi], + [ + entityPresentationApiRef, + DefaultEntityPresentationApi.create({ catalogApi }), + ], ]} > { spec: { type: 'team', children: [], - profile: { - displayName: 'Team A', - }, }, }, ] as Entity[], @@ -91,6 +99,10 @@ describe('MyGroupsSidebarItem Test', () => { apis={[ [identityApiRef, identityApi], [catalogApiRef, catalogApi], + [ + entityPresentationApiRef, + DefaultEntityPresentationApi.create({ catalogApi }), + ], ]} > { }); }); + async function renderSideBarWithUserGroups() { + const identityApi = mockApis.identity({ + userEntityRef: 'user:default/nigel.manning', + ownershipEntityRefs: ['user:default/nigel.manning'], + }); + const catalogApi = catalogApiMock.mock({ + getEntities: async () => ({ + items: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'team-a', + namespace: 'default', + }, + spec: { + type: 'team', + children: [], + }, + }, + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'team-b', + title: 'Team B', + namespace: 'default', + }, + spec: { + type: 'team', + children: [], + }, + }, + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'team-c', + namespace: 'default', + }, + spec: { + type: 'team', + children: [], + profile: { + displayName: 'Team C', + }, + }, + }, + ] as Entity[], + }), + }); + + await renderInTestApp( + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ); + } + describe('For users that are members of multiple groups', () => { - it('MyGroupsSidebarItem should display a sub-menu with all their groups, with displaying the title through displayName, and a link to each group', async () => { + beforeEach(async () => { + await renderSideBarWithUserGroups(); + }); + + it('MyGroupsSidebarItem should display a sub-menu with all their groups and a link to each group', async () => { + expect(screen.getByLabelText('My Squads')).toBeInTheDocument(); + }); + + it('MyGroupsSidebarItem should display the displayName for each group in the list instead of the metadata name using the entityPresentationApi', async () => { + await userEvent.hover(screen.getByLabelText('My Squads')); + expect(screen.getByText('team-a')).toBeInTheDocument(); + expect(screen.getByText('Team B')).toBeInTheDocument(); + expect(screen.getByText('Team C')).toBeInTheDocument(); + }); + }); + + describe('When an additional filter is not provided', () => { + const entityPresentationApi = { + forEntity: jest.fn(), + }; + it('catalogApi.getEntities() should be called with the default filter', async () => { const identityApi = mockApis.identity({ - userEntityRef: 'user:default/nigel.manning', - ownershipEntityRefs: ['user:default/nigel.manning'], + userEntityRef: 'user:default/guest', + ownershipEntityRefs: ['user:default/guest'], }); - const catalogApi = catalogApiMock.mock({ - getEntities: async () => ({ - items: [ - { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Group', - metadata: { - name: 'team-a', - title: 'Team A', - namespace: 'default', - }, - spec: { - type: 'team', - children: [], - profile: { - displayName: 'Team A', - }, - }, - }, - { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Group', - metadata: { - name: 'team-b', - title: 'Team B', - namespace: 'default', - }, - spec: { - type: 'team', - children: [], - profile: { - displayName: 'Team B', - }, - }, - }, - { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Group', - metadata: { - name: 'team-c', - title: 'Team C', - namespace: 'default', - }, - spec: { - type: 'team', - children: [], - profile: { - displayName: 'Team C', - }, - }, - }, - ] as Entity[], - }), - }); - const rendered = await renderInTestApp( + const mockCatalogApi = catalogApiMock.mock(); + await renderInTestApp( { }, }, ); - - expect(rendered.getByLabelText('My Squads')).toBeInTheDocument(); - expect(rendered.getByLabelText('Team B')).toBeInTheDocument(); - expect(rendered.getByLabelText('Team A')).not.toBeInTheDocument(); - expect(rendered.getByLabelText('Team C')).toHaveAttribute( - 'href', - '/catalog/default/Group/team-c', - ); + expect(mockCatalogApi.getEntities).toHaveBeenCalledWith({ + filter: [ + { + kind: 'group', + 'relations.hasMember': 'user:default/guest', + }, + ], + fields: ['metadata', 'kind'], + }); }); + }); - it('MyGroupsSidebarItem should display the metadata name when the displayName property is not available in the entity', async () => { + describe('When an additional filter is provided', () => { + const entityPresentationApi = { + forEntity: jest.fn(), + }; + + it('catalogApi.getEntities() should be called with an additional filter item', async () => { const identityApi = mockApis.identity({ - userEntityRef: 'user:default/nigel.manning', - ownershipEntityRefs: ['user:default/nigel.manning'], + userEntityRef: 'user:default/guest', + ownershipEntityRefs: ['user:default/guest'], }); - const catalogApi = catalogApiMock.mock({ - getEntities: async () => ({ - items: [ - { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Group', - metadata: { - name: 'team-a', - title: 'Team A', - namespace: 'default', - }, - spec: { - type: 'team', - children: [], - }, - }, - { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Group', - metadata: { - name: 'team-b', - title: 'Team B', - namespace: 'default', - }, - spec: { - type: 'team', - children: [], - }, - }, - { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Group', - metadata: { - name: 'team-c', - title: 'Team C', - namespace: 'default', - }, - spec: { - type: 'team', - children: [], - }, - }, - ] as Entity[], - }), - }); - const rendered = await renderInTestApp( + const mockCatalogApi = catalogApiMock.mock(); + await renderInTestApp( , { @@ -271,87 +293,16 @@ describe('MyGroupsSidebarItem Test', () => { }, }, ); - expect(rendered.getByLabelText('Team-a')).toBeInTheDocument(); - expect(rendered.getByLabelText('Team-b')).toBeInTheDocument(); - expect(rendered.getByLabelText('Team-c')).toBeInTheDocument(); - }); - }); -}); - -describe('When an additional filter is not provided', () => { - it('catalogApi.getEntities() should be called with the default filter', async () => { - const identityApi = mockApis.identity({ - userEntityRef: 'user:default/guest', - ownershipEntityRefs: ['user:default/guest'], - }); - const mockCatalogApi = catalogApiMock.mock(); - await renderInTestApp( - - - , - { - mountedRoutes: { - '/catalog/:namespace/:kind/:name': entityRouteRef, - }, - }, - ); - expect(mockCatalogApi.getEntities).toHaveBeenCalledWith({ - filter: [ - { - kind: 'group', - 'relations.hasMember': 'user:default/guest', - }, - ], - fields: ['metadata', 'kind'], - }); - }); -}); - -describe('When an additional filter is provided', () => { - it('catalogApi.getEntities() should be called with an additional filter item', async () => { - const identityApi = mockApis.identity({ - userEntityRef: 'user:default/guest', - ownershipEntityRefs: ['user:default/guest'], - }); - const mockCatalogApi = catalogApiMock.mock(); - await renderInTestApp( - - - , - { - mountedRoutes: { - '/catalog/:namespace/:kind/:name': entityRouteRef, - }, - }, - ); - expect(mockCatalogApi.getEntities).toHaveBeenCalledWith({ - filter: [ - { - kind: 'group', - 'relations.hasMember': 'user:default/guest', - 'spec.type': 'team', - }, - ], - fields: ['metadata', 'kind'], + expect(mockCatalogApi.getEntities).toHaveBeenCalledWith({ + filter: [ + { + kind: 'group', + 'relations.hasMember': 'user:default/guest', + 'spec.type': 'team', + }, + ], + fields: ['metadata', 'kind'], + }); }); }); }); diff --git a/plugins/org/src/components/MyGroupsSidebarItem/MyGroupsSidebarItem.tsx b/plugins/org/src/components/MyGroupsSidebarItem/MyGroupsSidebarItem.tsx index 98b035000a..8292455115 100644 --- a/plugins/org/src/components/MyGroupsSidebarItem/MyGroupsSidebarItem.tsx +++ b/plugins/org/src/components/MyGroupsSidebarItem/MyGroupsSidebarItem.tsx @@ -37,7 +37,8 @@ import { entityRouteRef, } from '@backstage/plugin-catalog-react'; import { getCompoundEntityRef } from '@backstage/catalog-model'; -import { EntityDisplayName } from '@backstage/plugin-catalog-react'; + +import { entityPresentationApiRef } from '@backstage/plugin-catalog-react'; /** * MyGroupsSidebarItem can be added to your sidebar providing quick access to groups the logged in user is a member of @@ -55,6 +56,7 @@ export const MyGroupsSidebarItem = (props: { const identityApi = useApi(identityApiRef); const catalogApi: CatalogApi = useApi(catalogApiRef); const catalogEntityRoute = useRouteRef(entityRouteRef); + const entityPresentationApi = useApi(entityPresentationApiRef); const { value: groups } = useAsync(async () => { const profile = await identityApi.getBackstageIdentity(); @@ -67,13 +69,11 @@ export const MyGroupsSidebarItem = (props: { ...(filter ?? {}), }, ], - fields: ['metadata', 'kind', 'spec'], + fields: ['metadata', 'kind'], }); - return response.items; }, []); - // Not a member of any groups if (!groups?.length) { return null; } @@ -94,10 +94,11 @@ export const MyGroupsSidebarItem = (props: { return ( - {groups?.map(function groupsMap(group) { + {groups?.map(group => { + const entityDisplayName = entityPresentationApi.forEntity(group); return ( } + title={entityDisplayName.snapshot.primaryTitle} subtitle={ group.metadata.namespace !== DEFAULT_NAMESPACE ? group.metadata.namespace