ServiceHealthCard.tsx110 lines · main
1import Link from 'next/link'
2import { Button, Card, CardContent, CardFooter, CardHeader, CardTitle, cn, Loading } from 'ui'
3import { LogsBarChart } from 'ui-patterns/LogsBarChart'
4
5import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
6import { getHealthStatus, type ServiceKey } from './ObservabilityOverview.utils'
7import NoDataPlaceholder from '@/components/ui/Charts/NoDataPlaceholder'
8
9const colorClassMap: Record<string, string> = {
10 muted: 'bg-muted',
11 destructive: 'bg-destructive',
12 warning: 'bg-warning',
13 brand: 'bg-brand',
14}
15
16export type ServiceHealthCardProps = {
17 serviceName: string
18 serviceKey: ServiceKey
19 total: number
20 errorRate: number
21 errorCount: number
22 warningCount: number
23 chartData: LogsBarChartDatum[]
24 reportUrl?: string // undefined if feature flag disabled or no report available
25 logsUrl: string
26 isLoading: boolean
27 onBarClick: (datum: any) => void
28 datetimeFormat: string
29}
30
31export const ServiceHealthCard = ({
32 serviceName,
33 serviceKey: _serviceKey,
34 total,
35 errorRate,
36 errorCount,
37 warningCount,
38 chartData,
39 reportUrl,
40 logsUrl,
41 isLoading,
42 onBarClick,
43 datetimeFormat,
44}: ServiceHealthCardProps) => {
45 const { color } = getHealthStatus(errorRate, total)
46
47 return (
48 <Card className="mb-0 md:mb-0 h-full flex flex-col">
49 <CardHeader className="flex flex-row items-start justify-between gap-2 space-y-0 pb-0 border-b-0">
50 <div className="flex flex-col">
51 <CardTitle className="text-foreground-light flex items-center gap-2 text-xs font-medium">
52 <div className={cn('w-1.5 h-1.5 rounded-full', colorClassMap[color] || 'bg-muted')} />
53 {serviceName}
54 </CardTitle>
55 <div className="flex items-start gap-6 mt-2">
56 <div className="flex flex-col">
57 <span className="text-foreground text-xl">{total.toLocaleString()}</span>
58 <span className="text-foreground-light text-xs">requests</span>
59 </div>
60 <div className="flex flex-col">
61 <span className="text-foreground text-xl">{errorRate.toFixed(1)}%</span>
62 <span className="text-foreground-light text-xs">error rate</span>
63 </div>
64 </div>
65 </div>
66 <div className="flex items-end gap-4 text-foreground-light">
67 <div className="flex flex-col items-end">
68 <div className="flex items-center gap-2">
69 <div className="w-1.5 h-1.5 bg-warning rounded-full" />
70 <span className="heading-meta">Warn</span>
71 </div>
72 <span className="text-foreground text-xl">{warningCount.toLocaleString()}</span>
73 </div>
74 <div className="flex flex-col items-end">
75 <div className="flex items-center gap-2">
76 <div className="w-1.5 h-1.5 bg-destructive rounded-full" />
77 <span className="heading-meta">Err</span>
78 </div>
79 <span className="text-foreground text-xl">{errorCount.toLocaleString()}</span>
80 </div>
81 </div>
82 </CardHeader>
83
84 <CardContent className="p-card pt-4 flex-1 overflow-hidden max-h-[200px]">
85 <Loading isFullHeight active={isLoading}>
86 <LogsBarChart
87 isFullHeight
88 data={chartData}
89 DateTimeFormat={datetimeFormat}
90 onBarClick={onBarClick}
91 EmptyState={
92 <NoDataPlaceholder size="small" message="No data for selected period" isFullHeight />
93 }
94 />
95 </Loading>
96 </CardContent>
97
98 <CardFooter className="border-t pt-3 pb-4 px-card flex gap-2">
99 {reportUrl && (
100 <Button type="default" size="tiny" asChild className="flex-1">
101 <Link href={reportUrl}>View Report</Link>
102 </Button>
103 )}
104 <Button type="default" size="tiny" asChild className="flex-1">
105 <Link href={logsUrl}>View Logs</Link>
106 </Button>
107 </CardFooter>
108 </Card>
109 )
110}