diff --git a/.changeset/sixty-rules-smell.md b/.changeset/sixty-rules-smell.md new file mode 100644 index 0000000000..7def15c06a --- /dev/null +++ b/.changeset/sixty-rules-smell.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +Some cleanup in how types and components are used; leverage `EntityRefLinks` diff --git a/.changeset/soft-cups-pull.md b/.changeset/soft-cups-pull.md new file mode 100644 index 0000000000..5461b9da0b --- /dev/null +++ b/.changeset/soft-cups-pull.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-api-docs': patch +'@backstage/plugin-catalog-react': patch +--- + +Make it possible to specify entity type to `useEntity` when it's known diff --git a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx index 0a4386e275..ce6f0658bf 100644 --- a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx +++ b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx @@ -15,8 +15,8 @@ */ import { ApiEntity } from '@backstage/catalog-model'; -import { useEntity } from '@backstage/plugin-catalog-react'; import { CardTab, TabbedCard, useApi } from '@backstage/core'; +import { useEntity } from '@backstage/plugin-catalog-react'; import { Alert } from '@material-ui/lab'; import React from 'react'; import { apiDocsConfigRef } from '../../config'; @@ -28,7 +28,7 @@ type Props = { }; export const ApiDefinitionCard = (_: Props) => { - const entity = useEntity().entity as ApiEntity; + const { entity } = useEntity(); const config = useApi(apiDocsConfigRef); const { getApiDefinitionWidget } = config; diff --git a/plugins/catalog-react/src/hooks/useEntity.ts b/plugins/catalog-react/src/hooks/useEntity.ts index faeeca11ed..0eb365d932 100644 --- a/plugins/catalog-react/src/hooks/useEntity.ts +++ b/plugins/catalog-react/src/hooks/useEntity.ts @@ -18,8 +18,8 @@ import { errorApiRef, useApi } from '@backstage/core'; import { createContext, useContext, useEffect } from 'react'; import { useNavigate } from 'react-router'; import { useAsync } from 'react-use'; -import { useEntityCompoundName } from './useEntityCompoundName'; import { catalogApiRef } from '../api'; +import { useEntityCompoundName } from './useEntityCompoundName'; type EntityLoadingStatus = { entity?: Entity; @@ -55,13 +55,9 @@ export const useEntityFromUrl = (): EntityLoadingStatus => { }; /** - * Grab Entity from the context and its current loading state. + * Grab the current entity from the context and its current loading state. */ -export const useEntity = () => { - const { entity, loading, error } = useContext<{ - entity: Entity; - loading: boolean; - error: Error; - }>(EntityContext as any); - return { entity, loading, error }; -}; +export function useEntity() { + const { entity, loading, error } = useContext(EntityContext); + return { entity: entity as T, loading, error }; +} diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index ce13f8391f..1e55b1b340 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -15,14 +15,13 @@ */ import { - Entity, GroupEntity, RELATION_CHILD_OF, RELATION_PARENT_OF, } from '@backstage/catalog-model'; import { Avatar, InfoCard, InfoCardVariants } from '@backstage/core'; import { - entityRouteParams, + EntityRefLinks, getEntityRelations, useEntity, } from '@backstage/plugin-catalog-react'; @@ -41,34 +40,7 @@ import EmailIcon from '@material-ui/icons/Email'; import GroupIcon from '@material-ui/icons/Group'; import Alert from '@material-ui/lab/Alert'; import React from 'react'; -import { generatePath, Link as RouterLink } from 'react-router-dom'; -const GroupLink = ({ - groupName, - index = 0, -}: { - groupName: string; - index?: number; - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; -}) => { - const { entity } = useEntity(); - - return ( - <> - {index >= 1 ? ', ' : ''} - - [{groupName}] - - - ); -}; const CardTitle = ({ title }: { title: string }) => ( @@ -83,23 +55,25 @@ export const GroupProfileCard = ({ entity?: GroupEntity; variant?: InfoCardVariants; }) => { - const group = useEntity().entity as GroupEntity; + const { entity: group } = useEntity(); + if (!group) { + return Group not found; + } + const { metadata: { name, description }, spec: { profile }, } = group; - const parent = group?.relations - ?.filter(r => r.type === RELATION_CHILD_OF) - ?.map(groupItem => groupItem.target.name) - .toString(); const childRelations = getEntityRelations(group, RELATION_PARENT_OF, { + kind: 'Group', + }); + const parentRelations = getEntityRelations(group, RELATION_CHILD_OF, { kind: 'group', }); const displayName = profile?.displayName ?? name; - - if (!group) return User not found; + const emailHref = profile?.email ? `mailto:${profile.email}` : undefined; return ( - {profile.email} + + {profile.email} + )} - {parent ? ( + + {parentRelations.length ? ( @@ -131,11 +108,15 @@ export const GroupProfileCard = ({ - + ) : null} - {childRelations?.length ? ( + + {childRelations.length ? ( @@ -143,13 +124,10 @@ export const GroupProfileCard = ({ - {childRelations.map((children, index) => ( - - ))} + ) : null} diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx index 54bb82daad..852f2a9e98 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx @@ -19,11 +19,17 @@ import { RELATION_MEMBER_OF, UserEntity, } from '@backstage/catalog-model'; -import { Avatar, InfoCard, Progress, useApi } from '@backstage/core'; import { - useEntity, + Avatar, + InfoCard, + Progress, + ResponseErrorPanel, + useApi, +} from '@backstage/core'; +import { catalogApiRef, entityRouteParams, + useEntity, } from '@backstage/plugin-catalog-react'; import { Box, @@ -34,7 +40,6 @@ import { Theme, Typography, } from '@material-ui/core'; -import Alert from '@material-ui/lab/Alert'; import React from 'react'; import { generatePath, Link as RouterLink } from 'react-router-dom'; import { useAsync } from 'react-use'; @@ -110,7 +115,7 @@ export const MembersListCard = (_props: { /** @deprecated The entity is now grabbed from context instead */ entity?: GroupEntity; }) => { - const groupEntity = useEntity().entity as GroupEntity; + const { entity: groupEntity } = useEntity(); const { metadata: { name: groupName }, spec: { profile }, @@ -121,11 +126,9 @@ export const MembersListCard = (_props: { const { loading, error, value: members } = useAsync(async () => { const membersList = await catalogApi.getEntities({ - filter: { - kind: 'User', - }, + filter: { kind: 'User' }, }); - const groupMembersList = ((membersList.items as unknown) as Array).filter( + const groupMembersList = (membersList.items as UserEntity[]).filter( member => member?.relations?.some( r => @@ -140,7 +143,7 @@ export const MembersListCard = (_props: { if (loading) { return ; } else if (error) { - return {error.message}; + return ; } return ( @@ -150,7 +153,7 @@ export const MembersListCard = (_props: { subheader={`of ${displayName}`} > - {members && members.length ? ( + {members && members.length > 0 ? ( members.map(member => ( ; - }, [catalogApi]); + }, [catalogApi, entity]); if (loading) { return ; } else if (error) { - return {error.message}; + return ; } return ( diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx index 7c7d26c228..44683fdf7d 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx @@ -61,7 +61,7 @@ describe('UserSummary Test', () => { 'src', 'https://example.com/staff/calum.jpeg', ); - expect(rendered.getByText('[ExampleGroup]')).toHaveAttribute( + expect(rendered.getByText('ExampleGroup')).toHaveAttribute( 'href', '/catalog/default/group/ExampleGroup', ); diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx index cf558fd55d..585c0f32e0 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - Entity, - RELATION_MEMBER_OF, - UserEntity, -} from '@backstage/catalog-model'; +import { RELATION_MEMBER_OF, UserEntity } from '@backstage/catalog-model'; import { Avatar, InfoCard, InfoCardVariants } from '@backstage/core'; -import { entityRouteParams, useEntity } from '@backstage/plugin-catalog-react'; +import { + EntityRefLinks, + getEntityRelations, + useEntity, +} from '@backstage/plugin-catalog-react'; import { Box, Grid, @@ -35,30 +35,6 @@ import GroupIcon from '@material-ui/icons/Group'; import PersonIcon from '@material-ui/icons/Person'; import Alert from '@material-ui/lab/Alert'; import React from 'react'; -import { generatePath, Link as RouterLink } from 'react-router-dom'; - -const GroupLink = ({ - groupName, - index, - entity, -}: { - groupName: string; - index: number; - entity: Entity; -}) => ( - <> - {index >= 1 ? ', ' : ''} - - [{groupName}] - - -); const CardTitle = ({ title }: { title?: string }) => title ? ( @@ -75,22 +51,20 @@ export const UserProfileCard = ({ entity?: UserEntity; variant?: InfoCardVariants; }) => { - const user = useEntity().entity as UserEntity; - const { - metadata: { name: metaName }, - spec: { profile }, - } = user; - const groupNames = - user?.relations - ?.filter(r => r.type === RELATION_MEMBER_OF) - ?.map(group => group.target.name) || []; - const displayName = profile?.displayName ?? metaName; - + const { entity: user } = useEntity(); if (!user) { return User not found; } - const emailHref = profile?.email ? `mailto:${profile.email}` : ''; + const { + metadata: { name: metaName }, + spec: { profile }, + } = user; + const displayName = profile?.displayName ?? metaName; + const emailHref = profile?.email ? `mailto:${profile.email}` : undefined; + const memberOfRelations = getEntityRelations(user, RELATION_MEMBER_OF, { + kind: 'Group', + }); return ( } variant={variant}> @@ -104,7 +78,9 @@ export const UserProfileCard = ({ {profile?.email && ( - + + + {profile.email} @@ -119,14 +95,10 @@ export const UserProfileCard = ({ - {groupNames.map((groupName, index) => ( - - ))} +