diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 11b9555a12..caa3ac7a56 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -48,6 +48,7 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", + "material-ui-popup-state": "^1.9.3", "classnames": "^2.2.6", "jwt-decode": "^3.1.0", "lodash": "^4.17.21", diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx index 06a632232e..62be932f44 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx @@ -19,13 +19,34 @@ import { CompoundEntityRef, DEFAULT_NAMESPACE, parseEntityRef, + isUserEntity, + isGroupEntity, } from '@backstage/catalog-model'; -import React, { forwardRef } from 'react'; +import React, { ForwardedRef, forwardRef } from 'react'; import { entityRouteRef } from '../../routes'; import { humanizeEntityRef } from './humanize'; import { Link, LinkProps } from '@backstage/core-components'; -import { useRouteRef } from '@backstage/core-plugin-api'; -import { Tooltip } from '@material-ui/core'; +import { useApi, useRouteRef } from '@backstage/core-plugin-api'; +import { + Button, + Tooltip, + Typography, + CardContent, + Card, + CardActions, + makeStyles, +} 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 useAsync from 'react-use/lib/useAsync'; +import { catalogApiRef } from '../../api'; /** * Props for {@link EntityRefLink}. @@ -39,6 +60,101 @@ export type EntityRefLinkProps = { children?: React.ReactNode; } & Omit; +type PeekAheadPopoverProps = { + popupState: PopupState; + entityRef: CompoundEntityRef; + ref: ForwardedRef; +}; + +const useStyles = makeStyles(() => { + return { + popover: { + width: '80em', + minWidth: '80em', + maxWidth: '80em', + }, + card: { + width: '100%', + }, + }; +}); + +export const PeekAheadPopover = ({ + popupState, + entityRef, + ref, +}: PeekAheadPopoverProps) => { + const catalogApi = useApi(catalogApiRef); + const entityRoute = useRouteRef(entityRouteRef); + const classes = useStyles(); + + const { value, loading, error } = useAsync(async () => { + if (popupState.isOpen) { + return catalogApi.getEntityByRef(entityRef); + } + return undefined; + }, [popupState]); + + if (loading) { + return null; + } + + return ( + + + + {entityRef.namespace} + + {entityRef.name} + + {entityRef.kind} + + {error && error.message} + {value && ( + <> + {value.metadata.description} +
+
+ {value.spec?.type} + + )} +
+
+ + {value && + (isUserEntity(value) || isGroupEntity(value)) && + value.spec.profile?.email && ( + + + + )} + + + + + + +
+
+ ); +}; /** * Shows a clickable link to an entity. * @@ -48,6 +164,10 @@ 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; @@ -78,16 +198,33 @@ export const EntityRefLink = forwardRef( ); const link = ( - - {children} - {!children && (title ?? formattedEntityRefTitle)} - + <> + + {children} + {!children && (title ?? formattedEntityRefTitle)} + + ); - return title ? ( - {link} - ) : ( - link + return ( + <> + {title ? ( + {link} + ) : ( + link + )} + + + ); }, ) as (props: EntityRefLinkProps) => JSX.Element;