import { geoCentroid } from 'd3-geo'
import sumBy from 'lodash/sumBy'
import { ChevronRight } from 'lucide-react'
import { useTheme } from 'next-themes'
import { Fragment, useRef, useState, type ReactNode } from 'react'
import { ComposableMap, Geographies, Geography, Marker, ZoomableGroup } from 'react-simple-maps'
import {
Alert,
AlertDescription,
AlertTitle,
Button,
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
WarningIcon,
} from 'ui'
import * as z from 'zod'
import { queryParamsToObject } from '../Reports.utils'
import { ReportWidgetProps, ReportWidgetRendererProps } from '../ReportWidget'
import { COUNTRY_LAT_LON } from '@/components/interfaces/ProjectCreation/ProjectCreation.constants'
import {
buildCountsByIso2,
computeMarkerRadius,
extractIso2FromFeatureProps,
getFillColor,
getFillOpacity,
isKnownCountryCode,
isMicroCountry,
iso2ToCountryName,
MAP_CHART_THEME,
} from '@/components/interfaces/Reports/utils/geo'
import {
jsonSyntaxHighlight,
TextFormatter,
} from '@/components/interfaces/Settings/Logs/LogsFormatters'
import Table from '@/components/to-be-cleaned/Table'
import AlertError from '@/components/ui/AlertError'
import BarChart from '@/components/ui/Charts/BarChart'
import { DataTableColumnStatusCode } from '@/components/ui/DataTable/DataTableColumn/DataTableColumnStatusCode'
import { useFillTimeseriesSorted } from '@/hooks/analytics/useFillTimeseriesSorted'
import { BASE_PATH } from '@/lib/constants'
import type { ResponseError } from '@/types'
export const NetworkTrafficRenderer = (
props: ReportWidgetProps<{
timestamp: string
ingress: number
egress: number
}>
) => {
const { data, error, isError } = useFillTimeseriesSorted({
data: props.data,
timestampKey: 'timestamp',
valueKey: ['ingress_mb', 'egress_mb'],
defaultValue: 0,
startDate: props.params?.iso_timestamp_start,
endDate: props.params?.iso_timestamp_end,
})
const totalIngress = sumBy(props.data, 'ingress_mb')
const totalEgress = sumBy(props.data, 'egress_mb')
function determinePrecision(valueInMb: number) {
return valueInMb < 0.001 ? 7 : totalIngress > 1 ? 2 : 4
}
if (!!props.error) {
const error = (
typeof props.error === 'string' ? { message: props.error } : props.error
) as ResponseError
return
} else if (isError) {
return (
Failed to retrieve network traffic
{error?.message ?? 'Unknown error'}
)
}
return (
)
}
export const TotalRequestsChartRenderer = (
props: ReportWidgetProps<{
timestamp: string
count: number
}>
) => {
const total = props.data.reduce((acc, datum) => {
return acc + datum.count
}, 0)
const { data, error, isError } = useFillTimeseriesSorted({
data: props.data,
timestampKey: 'timestamp',
valueKey: 'count',
defaultValue: 0,
startDate: props.params?.iso_timestamp_start,
endDate: props.params?.iso_timestamp_end,
})
if (!!props.error) {
const error = (
typeof props.error === 'string' ? { message: props.error } : props.error
) as ResponseError
return
} else if (isError) {
return (
Failed to retrieve total requests
{error?.message ?? 'Unknown error'}
)
}
return (
)
}
export const TopApiRoutesRenderer = (
props: ReportWidgetRendererProps<{
method: string
// shown for error table but not all requests table
status_code?: number
path: string
search: string
count: number
// used for response speed table only
avg?: number
}>
) => {
const [showMore, setShowMore] = useState(false)
const headerClasses = 'text-xs! py-2! p-0 font-bold bg-surface-200! border-x-0! rounded-none!'
const cellClasses = 'text-xs! py-2! border-x-0! rounded-none! align-middle'
if (props.data.length === 0) return null
return (
<>
Request
Count
{props.data[0].avg !== undefined && (
Avg
)}
>
}
body={
<>
{props.data.map((datum, index) => (
= 3 ? 'w-full h-full opacity-100' : '',
!showMore && index >= 3 ? ' w-0 h-0 translate-y-10 opacity-0' : '',
].join(' ')}
>
{(!showMore && index < 3) || showMore ? (
<>
{datum.count}
{props.data[0].avg !== undefined && (
{Number(datum.avg).toFixed(2)}ms
)}
>
) : null}
))}
>
}
/>
>
)
}
export const ErrorCountsChartRenderer = (
props: ReportWidgetProps<{
timestamp: string
count: number
}>
) => {
const total = props.data.reduce((acc, datum) => {
return acc + datum.count
}, 0)
const { data, error, isError } = useFillTimeseriesSorted({
data: props.data,
timestampKey: 'timestamp',
valueKey: 'count',
defaultValue: 0,
startDate: props.params?.iso_timestamp_start,
endDate: props.params?.iso_timestamp_end,
})
if (!!props.error) {
const error = (
typeof props.error === 'string' ? { message: props.error } : props.error
) as ResponseError
return
} else if (isError) {
return (
Failed to retrieve request errors
{error?.message ?? 'Unknown error'}
)
}
return (
)
}
export const ResponseSpeedChartRenderer = (
props: ReportWidgetProps<{
timestamp: string
avg: number
}>
) => {
const transformedData = props.data.map((datum) => ({
timestamp: datum.timestamp,
avg: datum.avg,
}))
const { data, error, isError } = useFillTimeseriesSorted({
data: transformedData,
timestampKey: 'timestamp',
valueKey: 'avg',
defaultValue: 0,
startDate: props.params?.iso_timestamp_start,
endDate: props.params?.iso_timestamp_end,
})
const lastAvg = props.data[props.data.length - 1]?.avg
if (!!props.error) {
const error = (
typeof props.error === 'string' ? { message: props.error } : props.error
) as ResponseError
return
} else if (isError) {
return (
Failed to retrieve response speeds
{error?.message ?? 'Unknown error'}
)
}
return (
)
}
interface RouteTdContentProps {
method: string
status_code?: number
path: string
search: string
}
const RouteTdContent = (datum: RouteTdContentProps) => (
{datum.status_code && (
)}
{datum.search ? (
) : (
No query parameters in this request
)}
)
export const RequestsByCountryMapRenderer = (
props: ReportWidgetProps<{
country: string | null
count: number
}>
) => {
const WORLD_TOPO_URL = `${BASE_PATH}/json/worldmap.json`
const containerRef = useRef(null)
const [hoverInfo, setHoverInfo] = useState<{
x: number
y: number
title: string
subtitle: string
visible: boolean
}>({ x: 0, y: 0, title: '', subtitle: '', visible: false })
const countsByIso2 = buildCountsByIso2(props.data)
const max = Object.values(countsByIso2).reduce((m, v) => (v > m ? v : m), 0)
const { resolvedTheme } = useTheme()
const theme = resolvedTheme === 'dark' ? MAP_CHART_THEME.dark : MAP_CHART_THEME.light
if (!!props.error) {
const AlertErrorSchema = z.object({ message: z.string() })
const parsed =
typeof props.error === 'string'
? { success: true, data: { message: props.error } }
: AlertErrorSchema.safeParse(props.error)
const alertError = parsed.success ? parsed.data : null
return
}
return (
{({ geographies }) => (
<>
{geographies.map((geo) => {
const title =
(geo.properties?.name as string) ||
(geo.properties?.NAME as string) ||
'Unknown'
const iso2 = extractIso2FromFeatureProps(
(geo.properties || undefined) as Record | undefined
)
const value = iso2 ? countsByIso2[iso2] || 0 : 0
const baseOpacity = getFillOpacity(value, max, theme)
const tooltipTitle = title
const tooltipSubtitle = `${value.toLocaleString()} requests`
return (
{
const rect = containerRef.current?.getBoundingClientRect()
const x = (rect ? e.clientX - rect.left : e.clientX) + 12
const y = (rect ? e.clientY - rect.top : e.clientY) + 12
setHoverInfo({
x,
y,
title: tooltipTitle,
subtitle: tooltipSubtitle,
visible: true,
})
}}
onMouseEnter={(e) => {
const rect = containerRef.current?.getBoundingClientRect()
const x = (rect ? e.clientX - rect.left : e.clientX) + 12
const y = (rect ? e.clientY - rect.top : e.clientY) + 12
setHoverInfo({
x,
y,
title: tooltipTitle,
subtitle: tooltipSubtitle,
visible: true,
})
}}
onMouseLeave={() => setHoverInfo((prev) => ({ ...prev, visible: false }))}
style={{
default: {
fill: getFillColor(value, max, theme),
stroke: theme.boundaryStroke,
strokeWidth: 0.4,
opacity: baseOpacity,
outline: 'none',
cursor: 'default',
},
hover: {
fill: getFillColor(value, max, theme),
stroke: 'transparent',
strokeWidth: 0,
opacity: Math.max(0, baseOpacity * 0.8),
outline: 'none',
cursor: 'default',
},
pressed: {
fill: getFillColor(value, max, theme),
stroke: 'transparent',
strokeWidth: 0,
opacity: Math.max(0, baseOpacity * 0.8),
outline: 'none',
cursor: 'default',
},
}}
aria-label={`${tooltipTitle} — ${tooltipSubtitle}`}
/>
)
})}
{geographies.map((geo) => {
const title =
(geo.properties?.name as string) ||
(geo.properties?.NAME as string) ||
'Unknown'
if (!isMicroCountry(title)) return null
const iso2 = extractIso2FromFeatureProps(
(geo.properties || undefined) as Record | undefined
)
const value = iso2 ? countsByIso2[iso2] || 0 : 0
if (value <= 0) return null
const [lon, lat] = geoCentroid(geo)
const r = computeMarkerRadius(value, max)
const tooltipTitle = title
const tooltipSubtitle = `${value.toLocaleString()} requests`
return (
{
const rect = containerRef.current?.getBoundingClientRect()
const x = (rect ? e.clientX - rect.left : e.clientX) + 12
const y = (rect ? e.clientY - rect.top : e.clientY) + 12
setHoverInfo({
x,
y,
title: tooltipTitle,
subtitle: tooltipSubtitle,
visible: true,
})
}}
onMouseEnter={(e) => {
const rect = containerRef.current?.getBoundingClientRect()
const x = (rect ? e.clientX - rect.left : e.clientX) + 12
const y = (rect ? e.clientY - rect.top : e.clientY) + 12
setHoverInfo({
x,
y,
title: tooltipTitle,
subtitle: tooltipSubtitle,
visible: true,
})
}}
onMouseLeave={() => setHoverInfo((prev) => ({ ...prev, visible: false }))}
>
)
})}
{(() => {
const present = new Set()
for (const g of geographies) {
const code = extractIso2FromFeatureProps(
(g.properties || undefined) as Record | undefined
)
if (code) present.add(code)
}
const markers: ReactNode[] = []
for (const iso2 in countsByIso2) {
const count = countsByIso2[iso2]
if (count <= 0) continue
// Do not render Antarctica
if (iso2.toUpperCase() === 'AQ') continue
if (present.has(iso2)) continue
if (!isKnownCountryCode(iso2)) continue
const ll = COUNTRY_LAT_LON[iso2]
const r = computeMarkerRadius(count, max)
const tooltipTitle = iso2ToCountryName(iso2)
const tooltipSubtitle = `${count.toLocaleString()} requests`
markers.push(
{
const rect = containerRef.current?.getBoundingClientRect()
const x = (rect ? e.clientX - rect.left : e.clientX) + 12
const y = (rect ? e.clientY - rect.top : e.clientY) + 12
setHoverInfo({
x,
y,
title: tooltipTitle,
subtitle: tooltipSubtitle,
visible: true,
})
}}
onMouseEnter={(e) => {
const rect = containerRef.current?.getBoundingClientRect()
const x = (rect ? e.clientX - rect.left : e.clientX) + 12
const y = (rect ? e.clientY - rect.top : e.clientY) + 12
setHoverInfo({
x,
y,
title: tooltipTitle,
subtitle: tooltipSubtitle,
visible: true,
})
}}
onMouseLeave={() => setHoverInfo((prev) => ({ ...prev, visible: false }))}
>
)
}
return markers
})()}
>
)}
{hoverInfo.visible && (
{hoverInfo.title}
{hoverInfo.subtitle}
)}
)
}