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..5013b309a7 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().entity; 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..c0e2660ad4 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 group = useEntity().entity; + if (!group) { + return User 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..3fc82ed3ec 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx @@ -21,9 +21,9 @@ import { } from '@backstage/catalog-model'; import { Avatar, InfoCard, Progress, useApi } from '@backstage/core'; import { - useEntity, catalogApiRef, entityRouteParams, + useEntity, } from '@backstage/plugin-catalog-react'; import { Box, @@ -110,7 +110,7 @@ export const MembersListCard = (_props: { /** @deprecated The entity is now grabbed from context instead */ entity?: GroupEntity; }) => { - const groupEntity = useEntity().entity as GroupEntity; + const groupEntity = useEntity().entity; const { metadata: { name: groupName }, spec: { profile }, @@ -121,11 +121,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 => @@ -150,7 +148,7 @@ export const MembersListCard = (_props: { subheader={`of ${displayName}`} > - {members && members.length ? ( + {members && members.length > 0 ? ( members.map(member => ( { '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..9648f2104a 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 user = useEntity().entity; 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) => ( - - ))} +