'use client' import dayjs from 'dayjs' import { useState } from 'react' import { cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui' import { CHART_COLORS, DateTimeFormats } from './Charts.constants' import { numberFormatter } from './Charts.utils' import { formatBytes } from 'lib/helpers' export interface ReportAttributes { id?: string titleTooltip?: string label: string attributes?: (MultiAttribute | false)[] defaultChartStyle?: 'bar' | 'line' | 'stackedAreaLine' hide?: boolean availableIn?: string[] hideChartType?: boolean format?: string className?: string showTooltip?: boolean showLegend?: boolean showTotal?: boolean showMaxValue?: boolean valuePrecision?: number docsUrl?: string syncId?: string showGrid?: boolean YAxisProps?: { width?: number tickFormatter?: (value: any) => string } hideHighlightedValue?: boolean } export type Provider = 'infra-monitoring' | 'daily-stats' | 'mock' | 'reference-line' | 'logs' export type MultiAttribute = { attribute: string provider: Provider label?: string color?: { light: string dark: string } statusCode?: string grantType?: string providerType?: string stackId?: string format?: string description?: string docsLink?: string isMaxValue?: boolean type?: 'line' | 'area-bar' omitFromTotal?: boolean tooltip?: string customValue?: number [key: string]: any /** * Manipulate the value of the attribute before it is displayed on the chart. * @param value - The value of the attribute. * @returns The manipulated value. */ manipulateValue?: (value: number) => number /** * Create a virtual attribute by combining values from other attributes. * Expression should use attribute names and basic math operators (+, -, *, /). * Example: 'disk_fs_used - pg_database_size - disk_fs_used_wal' */ combine?: string id?: string value?: number isReferenceLine?: boolean strokeDasharray?: string className?: string hide?: boolean enabled?: boolean } interface CustomIconProps { color: string } const CustomIcon = ({ color }: CustomIconProps) => ( ) const MaxConnectionsIcon = ({ color }: { color?: string }) => ( ) interface TooltipProps { active?: boolean payload?: any[] label?: string | number attributes?: MultiAttribute[] isPercentage?: boolean format?: string | ((value: unknown) => string) valuePrecision?: number showMaxValue?: boolean showTotal?: boolean isActiveHoveredChart?: boolean } const isMaxAttribute = (attributes?: MultiAttribute[]) => attributes?.find((a) => a.isMaxValue) /** * Calculate the total aggregate of the chart values * by summing the values of the attributes * that are not in the `ignoreAttributes` array */ export const calculateTotalChartAggregate = ( payload: { dataKey: string; value: number }[], ignoreAttributes?: string[] ) => payload ?.filter((p) => !ignoreAttributes?.includes(p.dataKey)) .reduce((acc, curr) => acc + curr.value, 0) const CustomTooltip = ({ active, payload, label, attributes, isPercentage, format, valuePrecision, showTotal, isActiveHoveredChart, }: TooltipProps) => { if (active && payload && payload.length) { const timestamp = payload[0].payload.timestamp const maxValueAttribute = isMaxAttribute(attributes) const maxValueData = maxValueAttribute && payload?.find((p: any) => p.dataKey === maxValueAttribute.attribute) const maxValue = maxValueData?.value const isRamChart = !payload?.some((p: any) => p.dataKey.toLowerCase() === 'ram_usage') && payload?.some((p: any) => p.dataKey.toLowerCase().includes('ram_')) const isDBSizeChart = payload?.some((p: any) => p.dataKey.toLowerCase().includes('disk_fs_')) || payload?.some((p: any) => p.dataKey.toLowerCase().includes('pg_database_size')) const isNetworkChart = payload?.some((p: any) => p.dataKey.toLowerCase().includes('network_')) const shouldFormatBytes = isRamChart || isDBSizeChart || isNetworkChart const attributesToIgnore = attributes?.filter((a) => a.omitFromTotal)?.map((a) => a.attribute) ?? [] const referenceLines = attributes ?.filter((attribute: MultiAttribute) => attribute?.provider === 'reference-line') ?.map((a: MultiAttribute) => a.attribute) ?? [] const attributesToIgnoreFromTotal = [ ...attributesToIgnore, ...referenceLines, ...(maxValueAttribute?.attribute ? [maxValueAttribute.attribute] : []), ] const total = showTotal && calculateTotalChartAggregate(payload, attributesToIgnoreFromTotal) const getIcon = (color: string, isMax: boolean) => isMax ? : const LabelItem = ({ entry }: { entry: any }) => { const attribute = attributes?.find((a: MultiAttribute) => a?.attribute === entry.name) const percentage = ((entry.value / maxValue) * 100).toFixed(valuePrecision) const isMax = entry.dataKey === maxValueAttribute?.attribute return (
{getIcon(entry.color, isMax)} {attribute?.label || entry.name} {shouldFormatBytes ? formatBytes(isNetworkChart ? Math.abs(entry.value) : entry.value, valuePrecision) : numberFormatter(entry.value, valuePrecision)} {isPercentage ? '%' : ''} {format === 'ms' ? 'ms' : ''} {/* Show percentage if max value is set */} {!!maxValueData && !isMax && !isPercentage && ( ({percentage}%) )}
) } return (

{dayjs(timestamp).format(DateTimeFormats.FULL_SECONDS)}

{payload .reverse() .filter((entry: any) => entry.value !== 0) .map((entry: any, index: number) => ( ))} {active && showTotal && (
Total
{shouldFormatBytes ? formatBytes(total as number, valuePrecision) : numberFormatter(total as number, valuePrecision)} {isPercentage ? '%' : ''} {format === 'ms' ? 'ms' : ''} {maxValueAttribute && !isPercentage && !isNaN((total as number) / maxValueData?.value) && isFinite((total as number) / maxValueData?.value) && ( ({(((total as number) / maxValueData?.value) * 100).toFixed(1)}%) )}
)}
) } return null } interface CustomLabelProps { payload?: any[] attributes?: MultiAttribute[] showMaxValue?: boolean onLabelHover?: (label: string | null) => void } const CustomLabel = ({ payload, attributes, showMaxValue, onLabelHover }: CustomLabelProps) => { const items = payload ?? [] const maxValueAttribute = isMaxAttribute(attributes) const [hoveredLabel, setHoveredLabel] = useState(null) const handleMouseEnter = (label: string) => { setHoveredLabel(label) onLabelHover?.(label) } const handleMouseLeave = () => { setHoveredLabel(null) onLabelHover?.(null) } const getIcon = (name: string, color: string) => { switch (name === maxValueAttribute?.attribute) { case true: return default: return } } const LabelItem = ({ entry }: { entry: any }) => { const attribute = attributes?.find((a) => a.attribute === entry.name) const isMax = entry.name === maxValueAttribute?.attribute const isHovered = hoveredLabel === entry.name const Label = () => (
{getIcon(entry.name, entry.color)} {attribute?.label || entry.name}
) if (!showMaxValue && isMax) return null return (
handleMouseEnter(entry.name)} onMouseOutCapture={handleMouseLeave} > {!!attribute?.tooltip ? ( {attribute.tooltip} ) : (
) } return (
{items?.map((entry, index) => )}
) } export { CustomLabel, CustomTooltip }