diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.tsx new file mode 100644 index 0000000000..c7d994628b --- /dev/null +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.tsx @@ -0,0 +1,188 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { entityRouteRef } from '../../routes'; +import useAsyncFn from 'react-use/lib/useAsyncFn'; +import { catalogApiRef } from '../../api'; +import React, { PropsWithChildren, useEffect } from 'react'; +import HoverPopover from 'material-ui-popup-state/HoverPopover'; +import { + bindHover, + bindPopover, + usePopupState, +} from 'material-ui-popup-state/hooks'; +import { + Box, + Button, + Card, + CardActions, + CardContent, + Chip, + makeStyles, + Tooltip, + Typography, +} from '@material-ui/core'; +import { Alert, Skeleton } from '@material-ui/lab'; +import EmailIcon from '@material-ui/icons/Email'; +import InfoIcon from '@material-ui/icons/Info'; +import { useApiHolder, useRouteRef } from '@backstage/core-plugin-api'; +import { + CompoundEntityRef, + isUserEntity, + isGroupEntity, +} from '@backstage/catalog-model'; +import { Link, Progress } from '@backstage/core-components'; + +export type EntityPeekAheadPopoverProps = { + entityRef: CompoundEntityRef; +}; + +const useStyles = makeStyles(() => { + return { + popoverPaper: { + width: '30em', + }, + descriptionTypography: { + overflow: 'hidden', + textOverflow: 'ellipsis', + display: '-webkit-box', + WebkitLineClamp: 2, + WebkitBoxOrient: 'vertical', + }, + }; +}); + +const maxTagChips = 4; + +export const EntityPeekAheadPopover = ({ + entityRef, + children, +}: PropsWithChildren) => { + const entityRoute = useRouteRef(entityRouteRef); + const classes = useStyles(); + const apiHolder = useApiHolder(); + const popupState = usePopupState({ + variant: 'popover', + popupId: 'entity-peek-ahead', + }); + + const [{ loading, error, value: entity }, load] = useAsyncFn(async () => { + const catalogApi = apiHolder.get(catalogApiRef); + if (catalogApi) { + const retrievedEntity = await catalogApi.getEntityByRef(entityRef); + if (!retrievedEntity) { + throw new Error(`${entityRef.name} was not found`); + } + return retrievedEntity; + } + return undefined; + }, [apiHolder, entityRef]); + + useEffect(() => { + if (popupState.isOpen && !entity && !error && !loading) { + load(); + } + }, [popupState.isOpen, load, entity, error, loading]); + + return ( + <> +
+ {children} +
+ + + {loading && } + + {entityRef.namespace} + + {entityRef.name} + + {error && {error.message}} + {entity ? ( + <> + {entity.kind} + + {/* {entity.metadata.description} */} + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed + do eiusmod tempor incididunt ut labore et dolore magna aliqua. + Ut enim ad minim veniam, quis nostrud exercitation ullamco + laboris nisi ut aliquip ex ea commodo consequat. Duis aute + irure dolor in reprehenderit in voluptate velit esse cillum + dolore eu fugiat nulla pariatur. Excepteur sint occaecat + cupidatat non proident, sunt in culpa qui officia deserunt + mollit anim id est laborum. + + {entity.spec?.type} + + {(entity.metadata.tags || []) + .slice(0, maxTagChips) + .map(tag => { + return ; + })} + {entity.metadata.tags?.length && + entity.metadata.tags?.length > maxTagChips && ( + + + + )} + + + ) : ( + <> + + + + + + )} + + + {entity && + (isUserEntity(entity) || isGroupEntity(entity)) && + entity.spec.profile?.email && ( + + + + )} + + + + + + + + + + ); +}; diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/index.ts b/plugins/catalog-react/src/components/EntityPeekAheadPopover/index.ts new file mode 100644 index 0000000000..476767931f --- /dev/null +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { EntityPeekAheadPopover } from './EntityPeekAheadPopover'; diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx index 7ebd828a4c..813195cb70 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx @@ -19,38 +19,13 @@ import { CompoundEntityRef, DEFAULT_NAMESPACE, parseEntityRef, - isUserEntity, - isGroupEntity, } from '@backstage/catalog-model'; -import React, { forwardRef, useEffect } from 'react'; +import React, { forwardRef } from 'react'; import { entityRouteRef } from '../../routes'; import { humanizeEntityRef } from './humanize'; -import { Link, LinkProps, Progress } from '@backstage/core-components'; -import { useApiHolder, useRouteRef } from '@backstage/core-plugin-api'; -import { - Button, - Tooltip, - Typography, - CardContent, - Card, - CardActions, - makeStyles, - Box, - Chip, -} from '@material-ui/core'; -import { - usePopupState, - bindPopover, - bindHover, - PopupState, -} from 'material-ui-popup-state/hooks'; -import HoverPopover from 'material-ui-popup-state/HoverPopover'; -import EmailIcon from '@material-ui/icons/Email'; -import InfoIcon from '@material-ui/icons/Info'; -import { catalogApiRef } from '../../api'; -import { Alert, Skeleton } from '@material-ui/lab'; -import useAsyncFn from 'react-use/lib/useAsyncFn'; - +import { Link, LinkProps } from '@backstage/core-components'; +import { useRouteRef } from '@backstage/core-plugin-api'; +import { EntityPeekAheadPopover } from '../EntityPeekAheadPopover'; /** * Props for {@link EntityRefLink}. * @@ -63,129 +38,6 @@ export type EntityRefLinkProps = { children?: React.ReactNode; } & Omit; -type PeekAheadPopoverProps = { - popupState: PopupState; - entityRef: CompoundEntityRef; -}; - -const useStyles = makeStyles(() => { - return { - popoverPaper: { - width: '30em', - }, - descriptionTypography: { - overflow: 'hidden', - textOverflow: 'ellipsis', - display: '-webkit-box', - WebkitLineClamp: 2, - WebkitBoxOrient: 'vertical', - }, - }; -}); - -const maxTagChips = 4; - -export const PeekAheadPopover = ({ - popupState, - entityRef, -}: PeekAheadPopoverProps) => { - const entityRoute = useRouteRef(entityRouteRef); - const classes = useStyles(); - const apiHolder = useApiHolder(); - - const [{ loading, error, value: entity }, load] = useAsyncFn(async () => { - const catalogApi = apiHolder.get(catalogApiRef); - if (catalogApi) { - const retrievedEntity = await catalogApi.getEntityByRef(entityRef); - if (!retrievedEntity) { - throw new Error(`${entityRef.name} was not found`); - } - return retrievedEntity; - } - return undefined; - }, [apiHolder, entityRef]); - - useEffect(() => { - if (popupState.isOpen && !entity && !error && !loading) { - load(); - } - }, [popupState.isOpen, load, entity, error, loading]); - - return ( - - - {loading && } - - {entityRef.namespace} - - {entityRef.name} - - {error && {error.message}} - {entity ? ( - <> - {entity.kind} - - {entity.metadata.description} - - {entity.spec?.type} - - {(entity.metadata.tags || []).slice(0, maxTagChips).map(tag => { - return ; - })} - {entity.metadata.tags?.length && - entity.metadata.tags?.length > maxTagChips && ( - - - - )} - - - ) : ( - <> - - - - - - )} - - - {entity && - (isUserEntity(entity) || isGroupEntity(entity)) && - entity.spec.profile?.email && ( - - - - )} - - - - - - - - - ); -}; /** * Shows a clickable link to an entity. * @@ -195,10 +47,6 @@ export const EntityRefLink = forwardRef( (props, ref) => { const { entityRef, defaultKind, title, children, ...linkProps } = props; const entityRoute = useRouteRef(entityRouteRef); - const popupState = usePopupState({ - variant: 'popover', - popupId: 'entity-peek-ahead', - }); let kind; let namespace; @@ -229,22 +77,12 @@ export const EntityRefLink = forwardRef( ); return ( - <> - + + {children} {!children && (title ?? formattedEntityRefTitle)} - - - + ); }, ) as (props: EntityRefLinkProps) => JSX.Element; diff --git a/plugins/catalog-react/src/components/index.ts b/plugins/catalog-react/src/components/index.ts index c604306ead..a4380ddef0 100644 --- a/plugins/catalog-react/src/components/index.ts +++ b/plugins/catalog-react/src/components/index.ts @@ -19,6 +19,7 @@ export * from './EntityKindPicker'; export * from './EntityLifecyclePicker'; export * from './EntityOwnerPicker'; export * from './EntityRefLink'; +export * from './EntityPeekAheadPopover'; export * from './EntitySearchBar'; export * from './EntityTable'; export * from './EntityTagPicker';