Use the new group profile section in the group profile card
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 667 KiB |
@@ -0,0 +1,11 @@
|
||||
---
|
||||
'@backstage/plugin-org': patch
|
||||
---
|
||||
|
||||
Display the new `profile` fields (`displayName`, `email`, and `picture`) for
|
||||
groups on the `GroupProfileCard`.
|
||||
|
||||

|
||||
|
||||
This also resolves some cases where `profile` fields are missing for users or
|
||||
groups and for example falls back to displaying the entity name. Adds additional test data to the ACME Corp dataset.
|
||||
@@ -5,6 +5,10 @@ metadata:
|
||||
description: The backstage sub-department
|
||||
spec:
|
||||
type: sub-department
|
||||
profile:
|
||||
displayName: Backstage
|
||||
email: backstage@example.com
|
||||
picture: https://example.com/groups/backstage.jpeg
|
||||
parent: infrastructure
|
||||
ancestors: [infrastructure, acme-corp]
|
||||
children: [team-a, team-b]
|
||||
|
||||
@@ -5,6 +5,10 @@ metadata:
|
||||
description: The boxoffice sub-department
|
||||
spec:
|
||||
type: sub-department
|
||||
profile:
|
||||
displayName: Box Office
|
||||
email: boxoffice@example.com
|
||||
# Intentional no picture for testing
|
||||
parent: infrastructure
|
||||
ancestors: [infrastructure, acme-corp]
|
||||
children: [team-c, team-d]
|
||||
|
||||
@@ -5,6 +5,7 @@ metadata:
|
||||
description: The infra department
|
||||
spec:
|
||||
type: department
|
||||
# Intentional no profile for testing
|
||||
parent: acme-corp
|
||||
ancestors: [acme-corp]
|
||||
children: [backstage, boxoffice]
|
||||
|
||||
@@ -5,6 +5,10 @@ metadata:
|
||||
description: The acme-corp organization
|
||||
spec:
|
||||
type: organization
|
||||
profile:
|
||||
displayName: ACME Corp
|
||||
email: info@example.com
|
||||
picture: https://example.com/logo.jpeg
|
||||
ancestors: []
|
||||
children: [infrastructure]
|
||||
descendants:
|
||||
|
||||
@@ -5,6 +5,10 @@ metadata:
|
||||
description: Team A
|
||||
spec:
|
||||
type: team
|
||||
profile:
|
||||
# Intentional no displayName for testing
|
||||
email: team-a@example.com
|
||||
picture: https://example.com/groups/team-a.jpeg
|
||||
parent: backstage
|
||||
ancestors: [backstage, infrastructure, acme-corp]
|
||||
children: []
|
||||
@@ -16,7 +20,7 @@ metadata:
|
||||
name: breanna.davison
|
||||
spec:
|
||||
profile:
|
||||
displayName: Breanna Davison
|
||||
# Intentional no displayName for testing
|
||||
email: breanna-davison@example.com
|
||||
picture: https://example.com/staff/breanna.jpeg
|
||||
memberOf: [team-a]
|
||||
|
||||
@@ -5,6 +5,10 @@ metadata:
|
||||
description: Team B
|
||||
spec:
|
||||
type: team
|
||||
profile:
|
||||
displayName: Team B
|
||||
email: team-b@example.com
|
||||
picture: https://example.com/groups/team-b.jpeg
|
||||
parent: backstage
|
||||
ancestors: [backstage, infrastructure, acme-corp]
|
||||
children: []
|
||||
|
||||
@@ -5,6 +5,10 @@ metadata:
|
||||
description: Team C
|
||||
spec:
|
||||
type: team
|
||||
profile:
|
||||
displayName: Team C
|
||||
email: team-c@example.com
|
||||
picture: https://example.com/groups/team-c.jpeg
|
||||
parent: boxoffice
|
||||
ancestors: [boxoffice, infrastructure, acme-corp]
|
||||
children: []
|
||||
|
||||
@@ -5,6 +5,10 @@ metadata:
|
||||
description: Team D
|
||||
spec:
|
||||
type: team
|
||||
profile:
|
||||
displayName: Team D
|
||||
email: team-d@example.com
|
||||
picture: https://example.com/groups/team-d.jpeg
|
||||
parent: boxoffice
|
||||
ancestors: [boxoffice, infrastructure, acme-corp]
|
||||
children: []
|
||||
|
||||
@@ -253,8 +253,10 @@ describe('read microsoft graph', () => {
|
||||
});
|
||||
expect(client.getGroupMembers).toBeCalledTimes(1);
|
||||
expect(client.getGroupMembers).toBeCalledWith('groupid');
|
||||
expect(client.getGroupPhotoWithSizeLimit).toBeCalledTimes(1);
|
||||
expect(client.getGroupPhotoWithSizeLimit).toBeCalledWith('groupid', 120);
|
||||
// TODO: Loading groups doesn't work right now as Microsoft Graph
|
||||
// doesn't allows this yet
|
||||
// expect(client.getGroupPhotoWithSizeLimit).toBeCalledTimes(1);
|
||||
// expect(client.getGroupPhotoWithSizeLimit).toBeCalledWith('groupid', 120);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -14,20 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Box, Grid, Link, Tooltip, Typography } from '@material-ui/core';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import { InfoCard } from '@backstage/core';
|
||||
import { entityRouteParams } from '@backstage/plugin-catalog';
|
||||
import {
|
||||
Entity,
|
||||
GroupEntity,
|
||||
RELATION_CHILD_OF,
|
||||
RELATION_PARENT_OF,
|
||||
} from '@backstage/catalog-model';
|
||||
import { Avatar, InfoCard } from '@backstage/core';
|
||||
import { entityRouteParams } from '@backstage/plugin-catalog';
|
||||
import { Box, Grid, Link, Tooltip, Typography } from '@material-ui/core';
|
||||
import AccountTreeIcon from '@material-ui/icons/AccountTree';
|
||||
import EmailIcon from '@material-ui/icons/Email';
|
||||
import GroupIcon from '@material-ui/icons/Group';
|
||||
import { Link as RouterLink, generatePath } from 'react-router-dom';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import React from 'react';
|
||||
import { generatePath, Link as RouterLink } from 'react-router-dom';
|
||||
|
||||
const GroupLink = ({
|
||||
groupName,
|
||||
@@ -68,6 +69,7 @@ export const GroupProfileCard = ({
|
||||
}) => {
|
||||
const {
|
||||
metadata: { name, description },
|
||||
spec: { profile },
|
||||
} = group;
|
||||
const parent = group?.relations
|
||||
?.filter(r => r.type === RELATION_CHILD_OF)
|
||||
@@ -78,16 +80,42 @@ export const GroupProfileCard = ({
|
||||
?.filter(r => r.type === RELATION_PARENT_OF)
|
||||
?.map(group => group.target.name);
|
||||
|
||||
const displayName = profile?.displayName ?? name;
|
||||
|
||||
if (!group) return <Alert severity="error">User not found</Alert>;
|
||||
|
||||
return (
|
||||
<InfoCard
|
||||
title={<CardTitle title={name} />}
|
||||
title={<CardTitle title={displayName} />}
|
||||
subheader={description}
|
||||
variant={variant}
|
||||
>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item>
|
||||
<Grid item xs={12} sm={2} xl={1}>
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="flex-start"
|
||||
justifyContent="center"
|
||||
height="100%"
|
||||
width="100%"
|
||||
>
|
||||
<Avatar displayName={displayName} picture={profile?.picture} />
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item md={10} xl={11}>
|
||||
{profile?.email && (
|
||||
<Typography variant="subtitle1">
|
||||
<Box display="flex" alignItems="center">
|
||||
<Tooltip title="Email">
|
||||
<EmailIcon fontSize="inherit" />
|
||||
</Tooltip>
|
||||
|
||||
<Box ml={1} display="inline">
|
||||
{profile.email}
|
||||
</Box>
|
||||
</Box>
|
||||
</Typography>
|
||||
)}
|
||||
{parent ? (
|
||||
<Typography variant="subtitle1">
|
||||
<Box display="flex" alignItems="center">
|
||||
|
||||
@@ -18,12 +18,12 @@ import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core';
|
||||
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Entity, GroupEntity } from '@backstage/catalog-model';
|
||||
import { MembersListCard } from './MembersListCard';
|
||||
|
||||
describe('MemberTab Test', () => {
|
||||
const groupEntity = {
|
||||
apiVersion: 'v1',
|
||||
const groupEntity: GroupEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
name: 'team-d',
|
||||
@@ -33,9 +33,7 @@ describe('MemberTab Test', () => {
|
||||
spec: {
|
||||
type: 'team',
|
||||
parent: 'boxoffice',
|
||||
ancestors: ['boxoffice', 'acme-corp'],
|
||||
children: [],
|
||||
descendants: [],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
import {
|
||||
Entity,
|
||||
GroupEntity,
|
||||
RELATION_MEMBER_OF,
|
||||
UserEntity,
|
||||
} from '@backstage/catalog-model';
|
||||
@@ -42,7 +43,9 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
borderRadius: '4px',
|
||||
overflow: 'visible',
|
||||
position: 'relative',
|
||||
margin: theme.spacing(3, 0, 0),
|
||||
margin: theme.spacing(3, 0, 1),
|
||||
flex: '1',
|
||||
minWidth: '0px',
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -55,12 +58,14 @@ const MemberComponent = ({
|
||||
groupEntity: Entity;
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const { name: metaName } = member.metadata;
|
||||
const { profile } = member.spec;
|
||||
const {
|
||||
metadata: { name: metaName },
|
||||
spec: { profile },
|
||||
} = member;
|
||||
const displayName = profile?.displayName ?? metaName;
|
||||
|
||||
return (
|
||||
<Grid item xs={12} sm={6} md={3} xl={2}>
|
||||
<Grid item container xs={12} sm={6} md={3} xl={2}>
|
||||
<Box className={classes.card}>
|
||||
<Box
|
||||
display="flex"
|
||||
@@ -100,13 +105,16 @@ const MemberComponent = ({
|
||||
export const MembersListCard = ({
|
||||
entity: groupEntity,
|
||||
}: {
|
||||
entity: Entity;
|
||||
entity: GroupEntity;
|
||||
}) => {
|
||||
const {
|
||||
metadata: { name: groupName },
|
||||
spec: { profile },
|
||||
} = groupEntity;
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
|
||||
const displayName = profile?.displayName ?? groupName;
|
||||
|
||||
const { loading, error, value: members } = useAsync(async () => {
|
||||
const membersList = await catalogApi.getEntities({
|
||||
filter: {
|
||||
@@ -133,7 +141,7 @@ export const MembersListCard = ({
|
||||
<Grid item>
|
||||
<InfoCard
|
||||
title={`Members (${members?.length || 0})`}
|
||||
subheader={`of ${groupName}`}
|
||||
subheader={`of ${displayName}`}
|
||||
>
|
||||
<Grid container spacing={3}>
|
||||
{members && members.length ? (
|
||||
|
||||
@@ -74,7 +74,6 @@ export const UserProfileCard = ({
|
||||
user?.relations
|
||||
?.filter(r => r.type === RELATION_MEMBER_OF)
|
||||
?.map(group => group.target.name) || [];
|
||||
|
||||
const displayName = profile?.displayName ?? metaName;
|
||||
|
||||
if (!user) {
|
||||
@@ -96,18 +95,19 @@ export const UserProfileCard = ({
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item md={10} xl={11}>
|
||||
<Typography variant="subtitle1">
|
||||
<Box display="flex" alignItems="center">
|
||||
<Tooltip title="Email">
|
||||
<EmailIcon fontSize="inherit" />
|
||||
</Tooltip>
|
||||
{profile?.email && (
|
||||
{profile?.email && (
|
||||
<Typography variant="subtitle1">
|
||||
<Box display="flex" alignItems="center">
|
||||
<Tooltip title="Email">
|
||||
<EmailIcon fontSize="inherit" />
|
||||
</Tooltip>
|
||||
|
||||
<Box ml={1} display="inline">
|
||||
{profile.email}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Typography>
|
||||
)}
|
||||
<Typography variant="subtitle1">
|
||||
<Box display="flex" alignItems="center">
|
||||
<Tooltip title="Member of">
|
||||
|
||||
Reference in New Issue
Block a user