datahub/datahub-web-react/src/app/preview/DefaultPreviewCard.tsx

279 lines
8.7 KiB
TypeScript

import { Image, Tooltip, Typography } from 'antd';
import React, { ReactNode } from 'react';
import { FolderOpenOutlined } from '@ant-design/icons';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import {
GlobalTags,
Owner,
GlossaryTerms,
SearchInsight,
Container,
Entity,
EntityType,
Domain,
} from '../../types.generated';
import { useEntityRegistry } from '../useEntityRegistry';
import AvatarsGroup from '../shared/avatar/AvatarsGroup';
import TagTermGroup from '../shared/tags/TagTermGroup';
import { ANTD_GRAY } from '../entity/shared/constants';
import NoMarkdownViewer from '../entity/shared/components/styled/StripMarkdownText';
import { getNumberWithOrdinal } from '../entity/shared/utils';
import { useEntityData } from '../entity/shared/EntityContext';
const PreviewContainer = styled.div`
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
`;
const PlatformInfo = styled.div`
margin-bottom: 8px;
display: flex;
align-items: center;
height: 24px;
`;
const TitleContainer = styled.div`
margin-bottom: 0px;
line-height: 30px;
`;
const PreviewImage = styled(Image)`
max-height: 18px;
width: auto;
object-fit: contain;
margin-right: 8px;
background-color: transparent;
`;
const EntityTitle = styled(Typography.Text)<{ $titleSizePx?: number }>`
&&& {
margin-right 8px;
font-size: ${(props) => props.$titleSizePx || 16}px;
font-weight: 600;
vertical-align: middle;
}
`;
const PlatformText = styled(Typography.Text)`
font-size: 12px;
line-height: 20px;
font-weight: 700;
color: ${ANTD_GRAY[7]};
`;
const EntityCountText = styled(Typography.Text)`
font-size: 12px;
line-height: 20px;
font-weight: 400;
color: ${ANTD_GRAY[7]};
`;
const PlatformDivider = styled.div`
display: inline-block;
padding-left: 10px;
margin-right: 10px;
border-right: 1px solid ${ANTD_GRAY[4]};
height: 21px;
vertical-align: text-top;
`;
const DescriptionContainer = styled.div`
margin-top: 5px;
color: ${ANTD_GRAY[7]};
`;
const AvatarContainer = styled.div`
margin-top: 12px;
margin-right: 32px;
`;
const TagContainer = styled.div`
display: inline-block;
margin-left: 0px;
margin-top: -2px;
`;
const InsightContainer = styled.div`
margin-top: 12px;
`;
const InsightsText = styled(Typography.Text)`
font-size: 12px;
line-height: 20px;
font-weight: 600;
color: ${ANTD_GRAY[7]};
`;
const InsightIconContainer = styled.span`
margin-right: 4px;
`;
const TypeIcon = styled.span`
margin-right: 8px;
`;
const ContainerText = styled(Typography.Text)`
font-size: 12px;
line-height: 20px;
font-weight: 400;
color: ${ANTD_GRAY[9]};
`;
const ContainerIcon = styled(FolderOpenOutlined)`
&&& {
font-size: 12px;
margin-right: 4px;
}
`;
interface Props {
name: string;
logoUrl?: string;
logoComponent?: JSX.Element;
url: string;
description?: string;
type?: string;
typeIcon?: JSX.Element;
platform?: string;
qualifier?: string | null;
tags?: GlobalTags;
owners?: Array<Owner> | null;
snippet?: React.ReactNode;
insights?: Array<SearchInsight> | null;
glossaryTerms?: GlossaryTerms;
container?: Container;
domain?: Domain | null;
entityCount?: number;
dataTestID?: string;
titleSizePx?: number;
onClick?: () => void;
// this is provided by the impact analysis view. it is used to display
// how the listed node is connected to the source node
path?: Entity[];
}
export default function DefaultPreviewCard({
name,
logoUrl,
logoComponent,
url,
description,
type,
typeIcon,
platform,
// TODO(Gabe): support qualifier in the new preview card
// eslint-disable-next-line @typescript-eslint/no-unused-vars
qualifier,
tags,
owners,
snippet,
insights,
glossaryTerms,
domain,
container,
entityCount,
titleSizePx,
dataTestID,
onClick,
path,
}: Props) {
// sometimes these lists will be rendered inside an entity container (for example, in the case of impact analysis)
// in those cases, we may want to enrich the preview w/ context about the container entity
const { entityData } = useEntityData();
const entityRegistry = useEntityRegistry();
const insightViews: Array<ReactNode> = [
...(insights?.map((insight) => (
<>
<InsightIconContainer>{insight.icon}</InsightIconContainer>
<InsightsText>{insight.text}</InsightsText>
</>
)) || []),
];
if (snippet) {
insightViews.push(snippet);
}
return (
<PreviewContainer data-testid={dataTestID}>
<div>
<TitleContainer>
<Link to={url}>
<PlatformInfo>
{(logoUrl && <PreviewImage preview={false} src={logoUrl} alt={platform || ''} />) ||
logoComponent}
{platform && <PlatformText>{platform}</PlatformText>}
{(logoUrl || logoComponent || platform) && <PlatformDivider />}
{typeIcon && <TypeIcon>{typeIcon}</TypeIcon>}
<PlatformText>{type}</PlatformText>
{container && (
<Link to={entityRegistry.getEntityUrl(EntityType.Container, container?.urn)}>
<PlatformDivider />
<ContainerIcon
style={{
color: ANTD_GRAY[9],
}}
/>
<ContainerText>
{entityRegistry.getDisplayName(EntityType.Container, container)}
</ContainerText>
</Link>
)}
{entityCount && entityCount > 0 ? (
<>
<PlatformDivider />
<EntityCountText>{entityCount.toLocaleString()} entities</EntityCountText>
</>
) : null}
{path && (
<span>
<PlatformDivider />
<Tooltip
title={`This entity is a ${getNumberWithOrdinal(
path?.length + 1,
)} degree connection to ${entityData?.name || 'the source entity'}`}
>
<PlatformText>{getNumberWithOrdinal(path?.length + 1)}</PlatformText>
</Tooltip>
</span>
)}
</PlatformInfo>
<EntityTitle onClick={onClick} $titleSizePx={titleSizePx}>
{name || ' '}
</EntityTitle>
</Link>
<TagContainer>
<TagTermGroup
domain={domain}
uneditableGlossaryTerms={glossaryTerms}
uneditableTags={tags}
maxShow={3}
/>
</TagContainer>
</TitleContainer>
{description && description.length > 0 && (
<DescriptionContainer>
<NoMarkdownViewer limit={200}>{description}</NoMarkdownViewer>
</DescriptionContainer>
)}
{owners && owners.length > 0 && (
<AvatarContainer>
<AvatarsGroup size={28} owners={owners} entityRegistry={entityRegistry} maxCount={4} />
</AvatarContainer>
)}
{insightViews.length > 0 && (
<InsightContainer>
{insightViews.map((insightView, index) => (
<span>
{insightView}
{index < insightViews.length - 1 && <PlatformDivider />}
</span>
))}
</InsightContainer>
)}
</div>
</PreviewContainer>
);
}