fix(MyGroupsSidebarItem): integrate entityPresentationApi for improved group display names
Signed-off-by: Dhruv Joshi <d.joshi@theaccessgroup.com>
This commit is contained in:
@@ -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 }),
|
||||
],
|
||||
]}
|
||||
>
|
||||
<MyGroupsSidebarItem
|
||||
@@ -78,9 +89,6 @@ describe('MyGroupsSidebarItem Test', () => {
|
||||
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 }),
|
||||
],
|
||||
]}
|
||||
>
|
||||
<MyGroupsSidebarItem
|
||||
@@ -113,71 +125,116 @@ describe('MyGroupsSidebarItem Test', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[identityApiRef, identityApi],
|
||||
[catalogApiRef, catalogApi],
|
||||
[
|
||||
entityPresentationApiRef,
|
||||
DefaultEntityPresentationApi.create({ catalogApi }),
|
||||
],
|
||||
]}
|
||||
>
|
||||
<MyGroupsSidebarItem
|
||||
singularTitle="My Squad"
|
||||
pluralTitle="My Squads"
|
||||
icon={GroupIcon}
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
{
|
||||
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(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[identityApiRef, identityApi],
|
||||
[catalogApiRef, catalogApi],
|
||||
[catalogApiRef, mockCatalogApi],
|
||||
[entityPresentationApiRef, entityPresentationApi],
|
||||
]}
|
||||
>
|
||||
<MyGroupsSidebarItem
|
||||
@@ -192,77 +249,42 @@ describe('MyGroupsSidebarItem Test', () => {
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
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(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[identityApiRef, identityApi],
|
||||
[catalogApiRef, catalogApi],
|
||||
[catalogApiRef, mockCatalogApi],
|
||||
[entityPresentationApiRef, entityPresentationApi],
|
||||
]}
|
||||
>
|
||||
<MyGroupsSidebarItem
|
||||
singularTitle="My Squad"
|
||||
pluralTitle="My Squads"
|
||||
icon={GroupIcon}
|
||||
filter={{ 'spec.type': 'team' }}
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
{
|
||||
@@ -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(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[identityApiRef, identityApi],
|
||||
[catalogApiRef, mockCatalogApi],
|
||||
]}
|
||||
>
|
||||
<MyGroupsSidebarItem
|
||||
singularTitle="My Squad"
|
||||
pluralTitle="My Squads"
|
||||
icon={GroupIcon}
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
{
|
||||
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(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[identityApiRef, identityApi],
|
||||
[catalogApiRef, mockCatalogApi],
|
||||
]}
|
||||
>
|
||||
<MyGroupsSidebarItem
|
||||
singularTitle="My Squad"
|
||||
pluralTitle="My Squads"
|
||||
icon={GroupIcon}
|
||||
filter={{ 'spec.type': 'team' }}
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
{
|
||||
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'],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 (
|
||||
<SidebarItem icon={icon} text={pluralTitle}>
|
||||
<SidebarSubmenu title={pluralTitle}>
|
||||
{groups?.map(function groupsMap(group) {
|
||||
{groups?.map(group => {
|
||||
const entityDisplayName = entityPresentationApi.forEntity(group);
|
||||
return (
|
||||
<SidebarSubmenuItem
|
||||
title={<EntityDisplayName entityRef={group} hideIcon />}
|
||||
title={entityDisplayName.snapshot.primaryTitle}
|
||||
subtitle={
|
||||
group.metadata.namespace !== DEFAULT_NAMESPACE
|
||||
? group.metadata.namespace
|
||||
|
||||
Reference in New Issue
Block a user