mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 20:05:06 +03:00
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
227 lines
5.7 KiB
TypeScript
227 lines
5.7 KiB
TypeScript
import { useQuery } from '@tanstack/react-query'
|
|
import { useMemo } from 'react'
|
|
|
|
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
|
|
import { LogsTableName } from '../Settings/Logs/Logs.constants'
|
|
import { genChartQuery } from '../Settings/Logs/Logs.utils'
|
|
import {
|
|
calculateAggregatedMetrics,
|
|
calculateDateRange,
|
|
calculateHealthMetrics,
|
|
transformToBarChartData,
|
|
type RawChartData,
|
|
} from './useServiceHealthMetrics.utils'
|
|
import { get } from '@/data/fetchers'
|
|
import { useFillTimeseriesSorted } from '@/hooks/analytics/useFillTimeseriesSorted'
|
|
import useTimeseriesUnixToIso from '@/hooks/analytics/useTimeseriesUnixToIso'
|
|
|
|
export type ServiceKey = 'db' | 'functions' | 'auth' | 'storage' | 'realtime' | 'postgrest'
|
|
|
|
export type ServiceHealthData = {
|
|
total: number
|
|
errorRate: number
|
|
successRate: number
|
|
errorCount: number
|
|
warningCount: number
|
|
okCount: number
|
|
eventChartData: LogsBarChartDatum[]
|
|
isLoading: boolean
|
|
error: unknown | null
|
|
refresh: () => void
|
|
}
|
|
|
|
type ServiceConfig = {
|
|
table: LogsTableName
|
|
enabled: boolean
|
|
}
|
|
|
|
const SERVICE_CONFIG: Record<ServiceKey, ServiceConfig> = {
|
|
db: { table: LogsTableName.POSTGRES, enabled: true },
|
|
auth: { table: LogsTableName.AUTH, enabled: true },
|
|
functions: { table: LogsTableName.FN_EDGE, enabled: true },
|
|
storage: { table: LogsTableName.STORAGE, enabled: true },
|
|
realtime: { table: LogsTableName.REALTIME, enabled: true },
|
|
postgrest: { table: LogsTableName.POSTGREST, enabled: true },
|
|
}
|
|
|
|
type ChartQueryResult = {
|
|
timestamp: string | number
|
|
ok_count: number
|
|
warning_count: number
|
|
error_count: number
|
|
}
|
|
|
|
/**
|
|
* Fetches service health metrics using the same logic as the logs pages
|
|
*/
|
|
const fetchServiceHealthMetrics = async (
|
|
projectRef: string,
|
|
table: LogsTableName,
|
|
startDate: string,
|
|
endDate: string,
|
|
signal?: AbortSignal
|
|
): Promise<ChartQueryResult[]> => {
|
|
const sql = genChartQuery(
|
|
table,
|
|
{
|
|
iso_timestamp_start: startDate,
|
|
iso_timestamp_end: endDate,
|
|
},
|
|
{}
|
|
)
|
|
|
|
const { data, error } = await get(`/platform/projects/{ref}/analytics/endpoints/logs.all`, {
|
|
params: {
|
|
path: { ref: projectRef },
|
|
query: {
|
|
sql,
|
|
iso_timestamp_start: startDate,
|
|
iso_timestamp_end: endDate,
|
|
},
|
|
},
|
|
signal,
|
|
})
|
|
|
|
if (error ?? data?.error) {
|
|
throw error ?? data?.error
|
|
}
|
|
|
|
return (data?.result ?? []) as ChartQueryResult[]
|
|
}
|
|
|
|
/**
|
|
* Hook to fetch health metrics for a single service
|
|
*/
|
|
const useServiceHealthQuery = ({
|
|
projectRef,
|
|
serviceKey,
|
|
startDate,
|
|
endDate,
|
|
enabled,
|
|
}: {
|
|
projectRef: string
|
|
serviceKey: ServiceKey
|
|
startDate: string
|
|
endDate: string
|
|
enabled: boolean
|
|
}) => {
|
|
const config = SERVICE_CONFIG[serviceKey]
|
|
const table = config.table
|
|
|
|
const queryResult = useQuery({
|
|
queryKey: ['service-health-metrics', projectRef, serviceKey, startDate, endDate, table],
|
|
queryFn: ({ signal }) =>
|
|
fetchServiceHealthMetrics(projectRef, table, startDate, endDate, signal),
|
|
enabled: enabled && config.enabled && Boolean(projectRef),
|
|
staleTime: 1000 * 60, // 1 minute
|
|
})
|
|
|
|
// Convert unix microseconds to ISO timestamps
|
|
const normalizedData = useTimeseriesUnixToIso(queryResult.data ?? [], 'timestamp')
|
|
|
|
// Fill gaps in timeseries
|
|
const { data: filledData } = useFillTimeseriesSorted({
|
|
data: normalizedData,
|
|
timestampKey: 'timestamp',
|
|
valueKey: 'ok_count',
|
|
defaultValue: 0,
|
|
startDate,
|
|
endDate,
|
|
})
|
|
|
|
// Transform to LogsBarChartDatum format
|
|
const eventChartData: LogsBarChartDatum[] = useMemo(
|
|
() => transformToBarChartData(filledData as RawChartData[]),
|
|
[filledData]
|
|
)
|
|
|
|
// Calculate metrics
|
|
const metrics = useMemo(() => calculateHealthMetrics(eventChartData), [eventChartData])
|
|
|
|
return {
|
|
...metrics,
|
|
eventChartData,
|
|
isLoading: queryResult.isLoading,
|
|
error: queryResult.error,
|
|
refresh: queryResult.refetch,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook to fetch observability overview data for all services using logs page queries
|
|
*/
|
|
export const useServiceHealthMetrics = (
|
|
projectRef: string,
|
|
interval: '1hr' | '1day' | '7day',
|
|
refreshKey: number
|
|
) => {
|
|
// Calculate date range based on interval
|
|
// refreshKey is intentionally included to force recalculation when user refreshes
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
const { startDate, endDate } = useMemo(() => calculateDateRange(interval), [interval, refreshKey])
|
|
|
|
const enabled = Boolean(projectRef)
|
|
|
|
// Fetch metrics for each service
|
|
const db = useServiceHealthQuery({ projectRef, serviceKey: 'db', startDate, endDate, enabled })
|
|
const auth = useServiceHealthQuery({
|
|
projectRef,
|
|
serviceKey: 'auth',
|
|
startDate,
|
|
endDate,
|
|
enabled,
|
|
})
|
|
const functions = useServiceHealthQuery({
|
|
projectRef,
|
|
serviceKey: 'functions',
|
|
startDate,
|
|
endDate,
|
|
enabled,
|
|
})
|
|
const storage = useServiceHealthQuery({
|
|
projectRef,
|
|
serviceKey: 'storage',
|
|
startDate,
|
|
endDate,
|
|
enabled,
|
|
})
|
|
const realtime = useServiceHealthQuery({
|
|
projectRef,
|
|
serviceKey: 'realtime',
|
|
startDate,
|
|
endDate,
|
|
enabled,
|
|
})
|
|
const postgrest = useServiceHealthQuery({
|
|
projectRef,
|
|
serviceKey: 'postgrest',
|
|
startDate,
|
|
endDate,
|
|
enabled,
|
|
})
|
|
|
|
const services: Record<ServiceKey, ServiceHealthData> = useMemo(
|
|
() => ({
|
|
db,
|
|
auth,
|
|
functions,
|
|
storage,
|
|
realtime,
|
|
postgrest,
|
|
}),
|
|
[db, auth, functions, storage, realtime, postgrest]
|
|
)
|
|
|
|
// Calculate aggregated metrics
|
|
const aggregated = useMemo(() => calculateAggregatedMetrics(Object.values(services)), [services])
|
|
|
|
const isLoading = Object.values(services).some((s) => s.isLoading)
|
|
|
|
return {
|
|
services,
|
|
aggregated,
|
|
isLoading,
|
|
endDate,
|
|
}
|
|
}
|