useServiceHealthMetrics.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import { useQuery } from '@tanstack/react-query'
  2. import { useMemo } from 'react'
  3. import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
  4. import { LogsTableName } from '../Settings/Logs/Logs.constants'
  5. import { genChartQuery } from '../Settings/Logs/Logs.utils'
  6. import {
  7. calculateAggregatedMetrics,
  8. calculateDateRange,
  9. calculateHealthMetrics,
  10. transformToBarChartData,
  11. type RawChartData,
  12. } from './useServiceHealthMetrics.utils'
  13. import { get } from '@/data/fetchers'
  14. import { useFillTimeseriesSorted } from '@/hooks/analytics/useFillTimeseriesSorted'
  15. import useTimeseriesUnixToIso from '@/hooks/analytics/useTimeseriesUnixToIso'
  16. export type ServiceKey = 'db' | 'functions' | 'auth' | 'storage' | 'realtime' | 'postgrest'
  17. export type ServiceHealthData = {
  18. total: number
  19. errorRate: number
  20. successRate: number
  21. errorCount: number
  22. warningCount: number
  23. okCount: number
  24. eventChartData: LogsBarChartDatum[]
  25. isLoading: boolean
  26. error: unknown | null
  27. refresh: () => void
  28. }
  29. type ServiceConfig = {
  30. table: LogsTableName
  31. enabled: boolean
  32. }
  33. const SERVICE_CONFIG: Record<ServiceKey, ServiceConfig> = {
  34. db: { table: LogsTableName.POSTGRES, enabled: true },
  35. auth: { table: LogsTableName.AUTH, enabled: true },
  36. functions: { table: LogsTableName.FN_EDGE, enabled: true },
  37. storage: { table: LogsTableName.STORAGE, enabled: true },
  38. realtime: { table: LogsTableName.REALTIME, enabled: true },
  39. postgrest: { table: LogsTableName.POSTGREST, enabled: true },
  40. }
  41. type ChartQueryResult = {
  42. timestamp: string | number
  43. ok_count: number
  44. warning_count: number
  45. error_count: number
  46. }
  47. /**
  48. * Fetches service health metrics using the same logic as the logs pages
  49. */
  50. const fetchServiceHealthMetrics = async (
  51. projectRef: string,
  52. table: LogsTableName,
  53. startDate: string,
  54. endDate: string,
  55. signal?: AbortSignal
  56. ): Promise<ChartQueryResult[]> => {
  57. const sql = genChartQuery(
  58. table,
  59. {
  60. iso_timestamp_start: startDate,
  61. iso_timestamp_end: endDate,
  62. },
  63. {}
  64. )
  65. const { data, error } = await get(`/platform/projects/{ref}/analytics/endpoints/logs.all`, {
  66. params: {
  67. path: { ref: projectRef },
  68. query: {
  69. sql,
  70. iso_timestamp_start: startDate,
  71. iso_timestamp_end: endDate,
  72. },
  73. },
  74. signal,
  75. })
  76. if (error ?? data?.error) {
  77. throw error ?? data?.error
  78. }
  79. return (data?.result ?? []) as ChartQueryResult[]
  80. }
  81. /**
  82. * Hook to fetch health metrics for a single service
  83. */
  84. const useServiceHealthQuery = ({
  85. projectRef,
  86. serviceKey,
  87. startDate,
  88. endDate,
  89. enabled,
  90. }: {
  91. projectRef: string
  92. serviceKey: ServiceKey
  93. startDate: string
  94. endDate: string
  95. enabled: boolean
  96. }) => {
  97. const config = SERVICE_CONFIG[serviceKey]
  98. const table = config.table
  99. const queryResult = useQuery({
  100. queryKey: ['service-health-metrics', projectRef, serviceKey, startDate, endDate, table],
  101. queryFn: ({ signal }) =>
  102. fetchServiceHealthMetrics(projectRef, table, startDate, endDate, signal),
  103. enabled: enabled && config.enabled && Boolean(projectRef),
  104. staleTime: 1000 * 60, // 1 minute
  105. })
  106. // Convert unix microseconds to ISO timestamps
  107. const normalizedData = useTimeseriesUnixToIso(queryResult.data ?? [], 'timestamp')
  108. // Fill gaps in timeseries
  109. const { data: filledData } = useFillTimeseriesSorted({
  110. data: normalizedData,
  111. timestampKey: 'timestamp',
  112. valueKey: 'ok_count',
  113. defaultValue: 0,
  114. startDate,
  115. endDate,
  116. })
  117. // Transform to LogsBarChartDatum format
  118. const eventChartData: LogsBarChartDatum[] = useMemo(
  119. () => transformToBarChartData(filledData as RawChartData[]),
  120. [filledData]
  121. )
  122. // Calculate metrics
  123. const metrics = useMemo(() => calculateHealthMetrics(eventChartData), [eventChartData])
  124. return {
  125. ...metrics,
  126. eventChartData,
  127. isLoading: queryResult.isLoading,
  128. error: queryResult.error,
  129. refresh: queryResult.refetch,
  130. }
  131. }
  132. /**
  133. * Hook to fetch observability overview data for all services using logs page queries
  134. */
  135. export const useServiceHealthMetrics = (
  136. projectRef: string,
  137. interval: '1hr' | '1day' | '7day',
  138. refreshKey: number
  139. ) => {
  140. // Calculate date range based on interval
  141. // refreshKey is intentionally included to force recalculation when user refreshes
  142. // eslint-disable-next-line react-hooks/exhaustive-deps
  143. const { startDate, endDate } = useMemo(() => calculateDateRange(interval), [interval, refreshKey])
  144. const enabled = Boolean(projectRef)
  145. // Fetch metrics for each service
  146. const db = useServiceHealthQuery({ projectRef, serviceKey: 'db', startDate, endDate, enabled })
  147. const auth = useServiceHealthQuery({
  148. projectRef,
  149. serviceKey: 'auth',
  150. startDate,
  151. endDate,
  152. enabled,
  153. })
  154. const functions = useServiceHealthQuery({
  155. projectRef,
  156. serviceKey: 'functions',
  157. startDate,
  158. endDate,
  159. enabled,
  160. })
  161. const storage = useServiceHealthQuery({
  162. projectRef,
  163. serviceKey: 'storage',
  164. startDate,
  165. endDate,
  166. enabled,
  167. })
  168. const realtime = useServiceHealthQuery({
  169. projectRef,
  170. serviceKey: 'realtime',
  171. startDate,
  172. endDate,
  173. enabled,
  174. })
  175. const postgrest = useServiceHealthQuery({
  176. projectRef,
  177. serviceKey: 'postgrest',
  178. startDate,
  179. endDate,
  180. enabled,
  181. })
  182. const services: Record<ServiceKey, ServiceHealthData> = useMemo(
  183. () => ({
  184. db,
  185. auth,
  186. functions,
  187. storage,
  188. realtime,
  189. postgrest,
  190. }),
  191. [db, auth, functions, storage, realtime, postgrest]
  192. )
  193. // Calculate aggregated metrics
  194. const aggregated = useMemo(() => calculateAggregatedMetrics(Object.values(services)), [services])
  195. const isLoading = Object.values(services).some((s) => s.isLoading)
  196. return {
  197. services,
  198. aggregated,
  199. isLoading,
  200. endDate,
  201. }
  202. }