ObservabilityOverview.tsx187 lines · main
1import { useQueryClient } from '@tanstack/react-query'
2import { useParams } from 'common'
3import dayjs from 'dayjs'
4import { RefreshCw } from 'lucide-react'
5import { useRouter } from 'next/router'
6import { useCallback, useMemo, useState } from 'react'
7import { Badge, Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
8
9import { DatabaseInfrastructureSection } from './DatabaseInfrastructureSection'
10import { useObservabilityOverviewData } from './ObservabilityOverview.utils'
11import { ObservabilityOverviewFooter } from './ObservabilityOverviewFooter'
12import { ServiceHealthTable } from './ServiceHealthTable'
13import { useSlowQueriesCount } from './useSlowQueriesCount'
14import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
15import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
16import { ChartIntervalDropdown } from '@/components/ui/Logs/ChartIntervalDropdown'
17import { CHART_INTERVALS } from '@/components/ui/Logs/logs.utils'
18import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
19import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
20
21type ChartIntervalKey = '1hr' | '1day' | '7day'
22
23export const ObservabilityOverview = () => {
24 const router = useRouter()
25 const { ref: projectRef } = useParams()
26 const { data: organization } = useSelectedOrganizationQuery()
27 const queryClient = useQueryClient()
28
29 const { projectStorageAll: storageSupported } = useIsFeatureEnabled(['project_storage:all'])
30
31 const DEFAULT_INTERVAL: ChartIntervalKey = '1day'
32 const [interval, setInterval] = useState<ChartIntervalKey>(DEFAULT_INTERVAL)
33 const [refreshKey, setRefreshKey] = useState(0)
34
35 const selectedInterval = CHART_INTERVALS.find((i) => i.key === interval) || CHART_INTERVALS[1]
36
37 const { datetimeFormat } = useMemo(() => {
38 const format = selectedInterval.format || 'MMM D, ha'
39 return { datetimeFormat: format }
40 }, [selectedInterval])
41
42 const overviewData = useObservabilityOverviewData(projectRef!, interval, refreshKey)
43
44 const { slowQueriesCount, isLoading: slowQueriesLoading } = useSlowQueriesCount(
45 projectRef,
46 refreshKey
47 )
48
49 const handleRefresh = useCallback(() => {
50 setRefreshKey((prev) => prev + 1)
51 queryClient.invalidateQueries({ queryKey: ['project-metrics'] })
52 queryClient.invalidateQueries({ queryKey: ['postgrest-overview-metrics'] })
53 queryClient.invalidateQueries({ queryKey: ['infra-monitoring'] })
54 queryClient.invalidateQueries({ queryKey: ['max-connections'] })
55 }, [queryClient])
56
57 const serviceBase = useMemo(
58 () => [
59 {
60 key: 'db' as const,
61 name: 'Database',
62 reportUrl: `/project/${projectRef}/observability/database`,
63 logsUrl: `/project/${projectRef}/logs/postgres-logs`,
64 enabled: true,
65 hasReport: true,
66 },
67 {
68 key: 'auth' as const,
69 name: 'Auth',
70 reportUrl: `/project/${projectRef}/observability/auth`,
71 logsUrl: `/project/${projectRef}/logs/auth-logs`,
72 enabled: true,
73 hasReport: true,
74 },
75 {
76 key: 'functions' as const,
77 name: 'Edge Functions',
78 reportUrl: `/project/${projectRef}/observability/edge-functions`,
79 logsUrl: `/project/${projectRef}/logs/edge-functions-logs`,
80 enabled: true,
81 hasReport: true,
82 },
83 {
84 key: 'realtime' as const,
85 name: 'Realtime',
86 reportUrl: `/project/${projectRef}/observability/realtime`,
87 logsUrl: `/project/${projectRef}/logs/realtime-logs`,
88 enabled: true,
89 hasReport: true,
90 },
91 {
92 key: 'storage' as const,
93 name: 'Storage',
94 reportUrl: `/project/${projectRef}/observability/storage`,
95 logsUrl: `/project/${projectRef}/logs/storage-logs`,
96 enabled: storageSupported,
97 hasReport: true,
98 },
99 {
100 key: 'postgrest' as const,
101 name: 'Data API',
102 reportUrl: `/project/${projectRef}/observability/postgrest`,
103 logsUrl: `/project/${projectRef}/logs/postgrest-logs`,
104 enabled: true,
105 hasReport: true,
106 },
107 ],
108 [projectRef, storageSupported]
109 )
110
111 const enabledServices = serviceBase.filter((s) => s.enabled)
112
113 const dbServiceData = overviewData.services.db
114
115 // Navigate to the log view scoped to the clicked bar's bucket window
116 const handleBarClick = useCallback(
117 (logsUrl: string) => (datum: any) => {
118 if (!datum?.timestamp) return
119
120 // datum.timestamp is already the UTC-truncated bucket boundary from timestamp_trunc(),
121 // so use it directly to avoid local-timezone startOf() misalignment (e.g. UTC+5:30).
122 const unit = interval === '1hr' ? 'minute' : 'hour'
123 const start = datum.timestamp
124 const end = dayjs.utc(datum.timestamp).add(1, unit).toISOString()
125
126 const queryParams = new URLSearchParams({ its: start, ite: end })
127 router.push(`${logsUrl}?${queryParams.toString()}`)
128 },
129 [router, interval]
130 )
131
132 return (
133 <ReportPadding>
134 <div className="flex flex-row justify-between items-center">
135 <div className="flex items-center gap-3">
136 <ReportHeader title="Overview" />
137 <Tooltip>
138 <TooltipTrigger asChild>
139 <Badge variant="warning">Beta</Badge>
140 </TooltipTrigger>
141 <TooltipContent>
142 <p>This page is subject to change</p>
143 </TooltipContent>
144 </Tooltip>
145 </div>
146 <div className="flex items-center gap-2">
147 <Button type="outline" icon={<RefreshCw size={14} />} onClick={handleRefresh}>
148 Refresh
149 </Button>
150 <ChartIntervalDropdown
151 value={interval}
152 onChange={(interval) => setInterval(interval as ChartIntervalKey)}
153 organizationSlug={organization?.slug}
154 dropdownAlign="end"
155 tooltipSide="left"
156 />
157 </div>
158 </div>
159
160 <div className="space-y-12 mt-8">
161 <DatabaseInfrastructureSection
162 interval={interval}
163 refreshKey={refreshKey}
164 dbErrorRate={dbServiceData.errorRate}
165 isLoading={dbServiceData.isLoading}
166 slowQueriesCount={slowQueriesCount}
167 slowQueriesLoading={slowQueriesLoading}
168 />
169
170 <ServiceHealthTable
171 services={enabledServices.map((service) => ({
172 key: service.key,
173 name: service.name,
174 description: '',
175 reportUrl: service.hasReport ? service.reportUrl : undefined,
176 logsUrl: service.logsUrl,
177 }))}
178 serviceData={overviewData.services}
179 onBarClick={handleBarClick}
180 datetimeFormat={datetimeFormat}
181 />
182 </div>
183
184 <ObservabilityOverviewFooter />
185 </ReportPadding>
186 )
187}