diff --git a/datahub-web-react/src/app/entity/chart/shared/ChartStatsSummary.tsx b/datahub-web-react/src/app/entity/chart/shared/ChartStatsSummary.tsx index 29b58ba756..566a9f9db5 100644 --- a/datahub-web-react/src/app/entity/chart/shared/ChartStatsSummary.tsx +++ b/datahub-web-react/src/app/entity/chart/shared/ChartStatsSummary.tsx @@ -6,6 +6,8 @@ import { formatNumberWithoutAbbreviation } from '../../../shared/formatNumber'; import { ANTD_GRAY } from '../../shared/constants'; import { toLocalDateTimeString, toRelativeTimeString } from '../../../shared/time/timeUtils'; import { StatsSummary } from '../../shared/components/styled/StatsSummary'; +import { countFormatter, needsFormatting } from '../../../../utils/formatter'; +import ExpandingStat from '../../dataset/shared/ExpandingStat'; const StatText = styled.span` color: ${ANTD_GRAY[8]}; @@ -33,9 +35,15 @@ export const ChartStatsSummary = ({ }: Props) => { const statsViews = [ (!!chartCount && ( - - {chartCount} charts - + ( + + {isExpanded ? formatNumberWithoutAbbreviation(chartCount) : countFormatter(chartCount)}{' '} + charts + + )} + /> )) || undefined, (!!viewCount && ( diff --git a/datahub-web-react/src/app/entity/dashboard/shared/DashboardStatsSummary.tsx b/datahub-web-react/src/app/entity/dashboard/shared/DashboardStatsSummary.tsx index 510c94efde..e8fb4c16ac 100644 --- a/datahub-web-react/src/app/entity/dashboard/shared/DashboardStatsSummary.tsx +++ b/datahub-web-react/src/app/entity/dashboard/shared/DashboardStatsSummary.tsx @@ -6,6 +6,8 @@ import { formatNumberWithoutAbbreviation } from '../../../shared/formatNumber'; import { ANTD_GRAY } from '../../shared/constants'; import { toLocalDateTimeString, toRelativeTimeString } from '../../../shared/time/timeUtils'; import { StatsSummary } from '../../shared/components/styled/StatsSummary'; +import { countFormatter, needsFormatting } from '../../../../utils/formatter'; +import ExpandingStat from '../../dataset/shared/ExpandingStat'; const StatText = styled.span` color: ${ANTD_GRAY[8]}; @@ -33,9 +35,15 @@ export const DashboardStatsSummary = ({ }: Props) => { const statsViews = [ (!!chartCount && ( - - {chartCount} charts - + ( + + {isExpanded ? formatNumberWithoutAbbreviation(chartCount) : countFormatter(chartCount)}{' '} + charts + + )} + /> )) || undefined, (!!viewCount && ( diff --git a/datahub-web-react/src/app/entity/dataset/shared/DatasetStatsSummary.tsx b/datahub-web-react/src/app/entity/dataset/shared/DatasetStatsSummary.tsx index 0dcc06bc2a..14f550de25 100644 --- a/datahub-web-react/src/app/entity/dataset/shared/DatasetStatsSummary.tsx +++ b/datahub-web-react/src/app/entity/dataset/shared/DatasetStatsSummary.tsx @@ -7,6 +7,8 @@ import { ANTD_GRAY } from '../../shared/constants'; import { toLocalDateTimeString, toRelativeTimeString } from '../../../shared/time/timeUtils'; import { StatsSummary } from '../../shared/components/styled/StatsSummary'; import { FormattedBytesStat } from './FormattedBytesStat'; +import { countFormatter, needsFormatting } from '../../../../utils/formatter'; +import ExpandingStat from './ExpandingStat'; const StatText = styled.span<{ color: string }>` color: ${(props) => props.color}; @@ -25,6 +27,7 @@ type Props = { uniqueUserCountLast30Days?: number | null; lastUpdatedMs?: number | null; color?: string; + mode?: 'normal' | 'tooltip-content'; }; export const DatasetStatsSummary = ({ @@ -36,20 +39,33 @@ export const DatasetStatsSummary = ({ uniqueUserCountLast30Days, lastUpdatedMs, color, + mode = 'normal', }: Props) => { - const displayedColor = color !== undefined ? color : ANTD_GRAY[7]; + const isTooltipMode = mode === 'tooltip-content'; + const displayedColor = isTooltipMode ? '' : color ?? ANTD_GRAY[7]; const statsViews = [ !!rowCount && ( - - - {formatNumberWithoutAbbreviation(rowCount)} rows - {!!columnCount && ( - <> - , {formatNumberWithoutAbbreviation(columnCount)} columns - + ( + + + {isExpanded ? formatNumberWithoutAbbreviation(rowCount) : countFormatter(rowCount)} rows + {!!columnCount && ( + <> + ,{' '} + + {isExpanded + ? formatNumberWithoutAbbreviation(columnCount) + : countFormatter(columnCount)} + {' '} + columns + + )} + )} - + /> ), !!sizeInBytes && ( diff --git a/datahub-web-react/src/app/entity/dataset/shared/ExpandingStat.tsx b/datahub-web-react/src/app/entity/dataset/shared/ExpandingStat.tsx new file mode 100644 index 0000000000..8101a696bf --- /dev/null +++ b/datahub-web-react/src/app/entity/dataset/shared/ExpandingStat.tsx @@ -0,0 +1,48 @@ +import React, { ReactNode, useEffect, useRef, useState } from 'react'; +import styled from 'styled-components'; + +const ExpandingStatContainer = styled.span<{ disabled: boolean; expanded: boolean; width: string }>` + overflow: hidden; + white-space: nowrap; + width: ${(props) => props.width}; + transition: width 250ms ease; +`; + +const ExpandingStat = ({ + disabled = false, + render, +}: { + disabled?: boolean; + render: (isExpanded: boolean) => ReactNode; +}) => { + const contentRef = useRef(null); + const [width, setWidth] = useState('inherit'); + const [isExpanded, setIsExpanded] = useState(false); + + useEffect(() => { + if (!contentRef.current) return; + setWidth(`${contentRef.current.offsetWidth}px`); + }, [isExpanded]); + + const onMouseEnter = () => { + if (!disabled) setIsExpanded(true); + }; + + const onMouseLeave = () => { + if (!disabled) setIsExpanded(false); + }; + + return ( + + {render(isExpanded)} + + ); +}; + +export default ExpandingStat; diff --git a/datahub-web-react/src/app/entity/shared/components/styled/StatsSummary.tsx b/datahub-web-react/src/app/entity/shared/components/styled/StatsSummary.tsx index 8146fa62bc..a0fe5ef031 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/StatsSummary.tsx +++ b/datahub-web-react/src/app/entity/shared/components/styled/StatsSummary.tsx @@ -8,15 +8,15 @@ type Props = { const StatsContainer = styled.div` margin-top: 8px; + display: flex; + align-items: center; `; const StatDivider = styled.div` - display: inline-block; padding-left: 10px; margin-right: 10px; border-right: 1px solid ${ANTD_GRAY[4]}; height: 21px; - vertical-align: text-top; `; export const StatsSummary = ({ stats }: Props) => { @@ -25,10 +25,10 @@ export const StatsSummary = ({ stats }: Props) => { {stats && stats.length > 0 && ( {stats.map((statView, index) => ( - + <> {statView} {index < stats.length - 1 && } - + ))} )} diff --git a/datahub-web-react/src/app/entity/shared/containers/profile/sidebar/Dataset/StatsSidebarSection.tsx b/datahub-web-react/src/app/entity/shared/containers/profile/sidebar/Dataset/StatsSidebarSection.tsx index a1c60fe00e..dfde26412a 100644 --- a/datahub-web-react/src/app/entity/shared/containers/profile/sidebar/Dataset/StatsSidebarSection.tsx +++ b/datahub-web-react/src/app/entity/shared/containers/profile/sidebar/Dataset/StatsSidebarSection.tsx @@ -8,7 +8,7 @@ import { ANTD_GRAY } from '../../../../constants'; import { useBaseEntity, useRouteToTab } from '../../../../EntityContext'; import { SidebarHeader } from '../SidebarHeader'; import { InfoItem } from '../../../../components/styled/InfoItem'; -import { countSeparator } from '../../../../../../../utils/formatter/index'; +import { formatNumberWithoutAbbreviation } from '../../../../../../shared/formatNumber'; const HeaderInfoBody = styled(Typography.Text)` font-size: 16px; @@ -83,7 +83,7 @@ export const SidebarStatsSection = () => { onClick={() => routeToTab({ tabName: 'Queries' })} width={INFO_ITEM_WIDTH_PX} > - {countSeparator(latestProfile?.rowCount)} + {formatNumberWithoutAbbreviation(latestProfile?.rowCount)} ) : null} {latestProfile?.columnCount ? ( diff --git a/datahub-web-react/src/app/entity/shared/tabs/Dataset/Stats/snapshot/TableStats.tsx b/datahub-web-react/src/app/entity/shared/tabs/Dataset/Stats/snapshot/TableStats.tsx index 62e5c7a627..eb39b9f420 100644 --- a/datahub-web-react/src/app/entity/shared/tabs/Dataset/Stats/snapshot/TableStats.tsx +++ b/datahub-web-react/src/app/entity/shared/tabs/Dataset/Stats/snapshot/TableStats.tsx @@ -4,8 +4,9 @@ import styled from 'styled-components'; import { CorpUser, Maybe, UserUsageCounts } from '../../../../../../../types.generated'; import { InfoItem } from '../../../../components/styled/InfoItem'; import { ANTD_GRAY } from '../../../../constants'; -import { countFormatter, countSeparator } from '../../../../../../../utils/formatter/index'; +import { countFormatter } from '../../../../../../../utils/formatter/index'; import { ExpandedActorGroup } from '../../../../components/styled/ExpandedActorGroup'; +import { formatNumberWithoutAbbreviation } from '../../../../../../shared/formatNumber'; type Props = { rowCount?: number; @@ -57,7 +58,7 @@ export default function TableStats({ {rowCount && ( - + {countFormatter(rowCount)} diff --git a/datahub-web-react/src/app/search/autoComplete/AutoCompleteItem.tsx b/datahub-web-react/src/app/search/autoComplete/AutoCompleteItem.tsx index 29cc549df0..c97d171b4c 100644 --- a/datahub-web-react/src/app/search/autoComplete/AutoCompleteItem.tsx +++ b/datahub-web-react/src/app/search/autoComplete/AutoCompleteItem.tsx @@ -39,7 +39,7 @@ export default function AutoCompleteItem({ query, entity }: Props) { return ( } placement="top" diff --git a/datahub-web-react/src/app/search/autoComplete/AutoCompleteTooltipContent.tsx b/datahub-web-react/src/app/search/autoComplete/AutoCompleteTooltipContent.tsx index 7a0e104cf1..dfe32c7805 100644 --- a/datahub-web-react/src/app/search/autoComplete/AutoCompleteTooltipContent.tsx +++ b/datahub-web-react/src/app/search/autoComplete/AutoCompleteTooltipContent.tsx @@ -53,7 +53,7 @@ export default function AutoCompleteTooltipContent({ entity }: Props) { } queryCountLast30Days={(entity as Dataset).statsSummary?.queryCountLast30Days} uniqueUserCountLast30Days={(entity as Dataset).statsSummary?.uniqueUserCountLast30Days} - color="" // need to pass in empty color so that tooltip decides the color here + mode="tooltip-content" /> )} diff --git a/datahub-web-react/src/utils/formatter/index.ts b/datahub-web-react/src/utils/formatter/index.ts index 270293960f..85919611e2 100644 --- a/datahub-web-react/src/utils/formatter/index.ts +++ b/datahub-web-react/src/utils/formatter/index.ts @@ -1,20 +1,31 @@ -const intlFormat = (num) => { - return new Intl.NumberFormat().format(Math.round(num * 10) / 10); -}; +type NumMapType = Record<'billion' | 'million' | 'thousand', { value: number; symbol: string }>; -export const countFormatter: (num: number) => string = (num: number) => { - if (num >= 1000000000) { - return `${intlFormat(num / 1000000000)}B`; - } - if (num >= 1000000) { - return `${intlFormat(num / 1000000)}M`; - } +const NumMap: NumMapType = { + billion: { + value: 1000000000, + symbol: 'B', + }, + million: { + value: 1000000, + symbol: 'M', + }, + thousand: { + value: 1000, + symbol: 'K', + }, +} as const; - if (num >= 1000) return `${intlFormat(num / 1000)}K`; +const isBillions = (num: number) => num >= NumMap.billion.value; +const isMillions = (num: number) => num >= NumMap.million.value; +const isThousands = (num: number) => num >= NumMap.thousand.value; +const intlFormat = (num: number) => new Intl.NumberFormat().format(Math.round(num * 10) / 10); + +export const needsFormatting = (num: number) => isThousands(num); + +export const countFormatter = (num: number) => { + if (isBillions(num)) return `${intlFormat(num / NumMap.billion.value)}${NumMap.billion.symbol}`; + if (isMillions(num)) return `${intlFormat(num / NumMap.million.value)}${NumMap.million.symbol}`; + if (isThousands(num)) return `${intlFormat(num / NumMap.thousand.value)}${NumMap.thousand.symbol}`; return intlFormat(num); }; - -export const countSeparator = (num) => { - return num.toLocaleString(); -};