| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- import { useQueryClient } from '@tanstack/react-query'
- import { useParams } from 'common'
- import dayjs from 'dayjs'
- import { RefreshCw } from 'lucide-react'
- import { useRouter } from 'next/router'
- import { useCallback, useMemo, useState } from 'react'
- import { Badge, Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
- import { DatabaseInfrastructureSection } from './DatabaseInfrastructureSection'
- import { useObservabilityOverviewData } from './ObservabilityOverview.utils'
- import { ObservabilityOverviewFooter } from './ObservabilityOverviewFooter'
- import { ServiceHealthTable } from './ServiceHealthTable'
- import { useSlowQueriesCount } from './useSlowQueriesCount'
- import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
- import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
- import { ChartIntervalDropdown } from '@/components/ui/Logs/ChartIntervalDropdown'
- import { CHART_INTERVALS } from '@/components/ui/Logs/logs.utils'
- import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
- import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
- type ChartIntervalKey = '1hr' | '1day' | '7day'
- export const ObservabilityOverview = () => {
- const router = useRouter()
- const { ref: projectRef } = useParams()
- const { data: organization } = useSelectedOrganizationQuery()
- const queryClient = useQueryClient()
- const { projectStorageAll: storageSupported } = useIsFeatureEnabled(['project_storage:all'])
- const DEFAULT_INTERVAL: ChartIntervalKey = '1day'
- const [interval, setInterval] = useState<ChartIntervalKey>(DEFAULT_INTERVAL)
- const [refreshKey, setRefreshKey] = useState(0)
- const selectedInterval = CHART_INTERVALS.find((i) => i.key === interval) || CHART_INTERVALS[1]
- const { datetimeFormat } = useMemo(() => {
- const format = selectedInterval.format || 'MMM D, ha'
- return { datetimeFormat: format }
- }, [selectedInterval])
- const overviewData = useObservabilityOverviewData(projectRef!, interval, refreshKey)
- const { slowQueriesCount, isLoading: slowQueriesLoading } = useSlowQueriesCount(
- projectRef,
- refreshKey
- )
- const handleRefresh = useCallback(() => {
- setRefreshKey((prev) => prev + 1)
- queryClient.invalidateQueries({ queryKey: ['project-metrics'] })
- queryClient.invalidateQueries({ queryKey: ['postgrest-overview-metrics'] })
- queryClient.invalidateQueries({ queryKey: ['infra-monitoring'] })
- queryClient.invalidateQueries({ queryKey: ['max-connections'] })
- }, [queryClient])
- const serviceBase = useMemo(
- () => [
- {
- key: 'db' as const,
- name: 'Database',
- reportUrl: `/project/${projectRef}/observability/database`,
- logsUrl: `/project/${projectRef}/logs/postgres-logs`,
- enabled: true,
- hasReport: true,
- },
- {
- key: 'auth' as const,
- name: 'Auth',
- reportUrl: `/project/${projectRef}/observability/auth`,
- logsUrl: `/project/${projectRef}/logs/auth-logs`,
- enabled: true,
- hasReport: true,
- },
- {
- key: 'functions' as const,
- name: 'Edge Functions',
- reportUrl: `/project/${projectRef}/observability/edge-functions`,
- logsUrl: `/project/${projectRef}/logs/edge-functions-logs`,
- enabled: true,
- hasReport: true,
- },
- {
- key: 'realtime' as const,
- name: 'Realtime',
- reportUrl: `/project/${projectRef}/observability/realtime`,
- logsUrl: `/project/${projectRef}/logs/realtime-logs`,
- enabled: true,
- hasReport: true,
- },
- {
- key: 'storage' as const,
- name: 'Storage',
- reportUrl: `/project/${projectRef}/observability/storage`,
- logsUrl: `/project/${projectRef}/logs/storage-logs`,
- enabled: storageSupported,
- hasReport: true,
- },
- {
- key: 'postgrest' as const,
- name: 'Data API',
- reportUrl: `/project/${projectRef}/observability/postgrest`,
- logsUrl: `/project/${projectRef}/logs/postgrest-logs`,
- enabled: true,
- hasReport: true,
- },
- ],
- [projectRef, storageSupported]
- )
- const enabledServices = serviceBase.filter((s) => s.enabled)
- const dbServiceData = overviewData.services.db
- // Navigate to the log view scoped to the clicked bar's bucket window
- const handleBarClick = useCallback(
- (logsUrl: string) => (datum: any) => {
- if (!datum?.timestamp) return
- // datum.timestamp is already the UTC-truncated bucket boundary from timestamp_trunc(),
- // so use it directly to avoid local-timezone startOf() misalignment (e.g. UTC+5:30).
- const unit = interval === '1hr' ? 'minute' : 'hour'
- const start = datum.timestamp
- const end = dayjs.utc(datum.timestamp).add(1, unit).toISOString()
- const queryParams = new URLSearchParams({ its: start, ite: end })
- router.push(`${logsUrl}?${queryParams.toString()}`)
- },
- [router, interval]
- )
- return (
- <ReportPadding>
- <div className="flex flex-row justify-between items-center">
- <div className="flex items-center gap-3">
- <ReportHeader title="Overview" />
- <Tooltip>
- <TooltipTrigger asChild>
- <Badge variant="warning">Beta</Badge>
- </TooltipTrigger>
- <TooltipContent>
- <p>This page is subject to change</p>
- </TooltipContent>
- </Tooltip>
- </div>
- <div className="flex items-center gap-2">
- <Button type="outline" icon={<RefreshCw size={14} />} onClick={handleRefresh}>
- Refresh
- </Button>
- <ChartIntervalDropdown
- value={interval}
- onChange={(interval) => setInterval(interval as ChartIntervalKey)}
- organizationSlug={organization?.slug}
- dropdownAlign="end"
- tooltipSide="left"
- />
- </div>
- </div>
- <div className="space-y-12 mt-8">
- <DatabaseInfrastructureSection
- interval={interval}
- refreshKey={refreshKey}
- dbErrorRate={dbServiceData.errorRate}
- isLoading={dbServiceData.isLoading}
- slowQueriesCount={slowQueriesCount}
- slowQueriesLoading={slowQueriesLoading}
- />
- <ServiceHealthTable
- services={enabledServices.map((service) => ({
- key: service.key,
- name: service.name,
- description: '',
- reportUrl: service.hasReport ? service.reportUrl : undefined,
- logsUrl: service.logsUrl,
- }))}
- serviceData={overviewData.services}
- onBarClick={handleBarClick}
- datetimeFormat={datetimeFormat}
- />
- </div>
- <ObservabilityOverviewFooter />
- </ReportPadding>
- )
- }
|