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 ( <> RequestCount {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}

)}
) }