Files
supabase/apps/studio/components/interfaces/ProjectHome/useProjectHomeServiceStatusModel.ts

235 lines
7.0 KiB
TypeScript

import { useBranchesQuery } from 'data/branches/branches-query'
import { useEdgeFunctionServiceStatusQuery } from 'data/service-status/edge-functions-status-query'
import { useProjectServiceStatusQuery } from 'data/service-status/service-status-query'
import dayjs from 'dayjs'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { DOCS_URL } from 'lib/constants'
import { extractDbSchema, type ProjectServiceStatus } from '../Home/ServiceStatus'
export const SERVICE_STATUS_THRESHOLD_MINUTES = 5
export type ProjectHomeServiceStatusProject = {
ref: string
parentRef: string
status?: string
inserted_at?: string
} | undefined
export type ProjectHomeServiceRow = {
name: string
error?: string
docsUrl?: string
isLoading: boolean
status: ProjectServiceStatus
logsUrl: string
}
export type ProjectHomeServiceStatusModel = {
services: ProjectHomeServiceRow[]
isLoadingChecks: boolean
allServicesOperational: boolean
isProjectNew: boolean
isProjectComingUp: boolean
overallStatusLabel: string
}
export function useProjectHomeServiceStatusModel(
projectRef: string | undefined,
project: ProjectHomeServiceStatusProject
): ProjectHomeServiceStatusModel {
const {
projectAuthAll: authEnabled,
projectEdgeFunctionAll: edgeFunctionsEnabled,
realtimeAll: realtimeEnabled,
projectStorageAll: storageEnabled,
} = useIsFeatureEnabled([
'project_auth:all',
'project_edge_function:all',
'realtime:all',
'project_storage:all',
])
const isBranch = Boolean(project && project.parentRef !== project.ref)
const { data: branches, isPending: isBranchesLoading } = useBranchesQuery(
{ projectRef: isBranch ? project?.parentRef : undefined },
{ enabled: isBranch && Boolean(projectRef) }
)
const currentBranch = isBranch
? branches?.find((branch) => branch.project_ref === projectRef)
: undefined
const { data: status, isPending: isLoading } = useProjectServiceStatusQuery(
{ projectRef },
{
enabled: Boolean(projectRef),
refetchInterval: (query) => {
const data = query.state.data
const isServiceUnhealthy = data?.some((service) => {
if (service.name === 'rest' && extractDbSchema(service) === '') {
return false
}
if (service.status === 'ACTIVE_HEALTHY') {
return false
}
return true
})
return isServiceUnhealthy ? 5000 : false
},
}
)
const { data: edgeFunctionsStatus } = useEdgeFunctionServiceStatusQuery(
{ projectRef },
{
enabled: Boolean(projectRef),
refetchInterval: (query) => (!query.state.data?.healthy ? 5000 : false),
}
)
const authStatus = status?.find((service) => service.name === 'auth')
const restStatus = status?.find((service) => service.name === 'rest')
const realtimeStatus = status?.find((service) => service.name === 'realtime')
const storageStatus = status?.find((service) => service.name === 'storage')
const dbStatus = status?.find((service) => service.name === 'db')
const isMigrationLoading =
project?.status === 'COMING_UP' ||
(isBranch &&
(isBranchesLoading ||
currentBranch?.status === 'CREATING_PROJECT' ||
currentBranch?.status === 'RUNNING_MIGRATIONS'))
const services: ProjectHomeServiceRow[] = [
{
name: 'Database',
error: undefined,
docsUrl: undefined,
isLoading: isLoading,
status: dbStatus?.status ?? 'UNHEALTHY',
logsUrl: '/logs/postgres-logs',
},
{
name: 'PostgREST',
error: restStatus?.error,
docsUrl: undefined,
isLoading,
status: extractDbSchema(restStatus) === '' ? 'DISABLED' : (restStatus?.status ?? 'UNHEALTHY'),
logsUrl: '/logs/postgrest-logs',
},
...(authEnabled
? [
{
name: 'Auth',
error: authStatus?.error,
docsUrl: undefined,
isLoading,
status: authStatus?.status ?? 'UNHEALTHY',
logsUrl: '/logs/auth-logs',
},
]
: []),
...(realtimeEnabled
? [
{
name: 'Realtime',
error: realtimeStatus?.error,
docsUrl: undefined,
isLoading,
status: realtimeStatus?.status ?? 'UNHEALTHY',
logsUrl: '/logs/realtime-logs',
},
]
: []),
...(storageEnabled
? [
{
name: 'Storage',
error: storageStatus?.error,
docsUrl: undefined,
isLoading,
status: storageStatus?.status ?? 'UNHEALTHY',
logsUrl: '/logs/storage-logs',
},
]
: []),
...(edgeFunctionsEnabled
? [
{
name: 'Edge Functions',
error: undefined,
docsUrl: `${DOCS_URL}/guides/functions/troubleshooting`,
isLoading,
status: edgeFunctionsStatus?.healthy
? ('ACTIVE_HEALTHY' as const)
: isLoading
? ('COMING_UP' as const)
: ('UNHEALTHY' as const),
logsUrl: '/logs/edge-functions-logs',
},
]
: []),
...(isBranch
? [
{
name: 'Migrations',
error: undefined,
docsUrl: undefined,
isLoading: isBranchesLoading,
status: isBranch
? currentBranch?.status === 'FUNCTIONS_DEPLOYED'
? ('ACTIVE_HEALTHY' as const)
: currentBranch?.status === 'FUNCTIONS_FAILED' ||
currentBranch?.status === 'MIGRATIONS_FAILED'
? ('UNHEALTHY' as const)
: ('COMING_UP' as const)
: isMigrationLoading
? 'COMING_UP'
: 'ACTIVE_HEALTHY',
logsUrl: isBranch ? '/branches' : '/logs/database-logs',
},
]
: []),
]
const isLoadingChecks = services.some((service) => service.isLoading)
const allServicesOperational = services.every(
(service) => service.status === 'ACTIVE_HEALTHY' || service.status === 'DISABLED'
)
const isProjectNew =
(project?.inserted_at != null &&
dayjs.utc().diff(dayjs.utc(project.inserted_at), 'minute') <
SERVICE_STATUS_THRESHOLD_MINUTES) ||
project?.status === 'COMING_UP' ||
(isBranch &&
(currentBranch?.status === 'CREATING_PROJECT' ||
currentBranch?.status === 'RUNNING_MIGRATIONS' ||
isMigrationLoading))
const isProjectComingUp = ['COMING_UP', 'UNKNOWN'].includes(project?.status ?? '')
const anyUnhealthy = services.some((service) => service.status === 'UNHEALTHY')
const anyComingUp =
isProjectComingUp || services.some((service) => service.status === 'COMING_UP')
const getOverallStatusLabel = (): string => {
if (isLoadingChecks) return 'Checking...'
if (anyComingUp) return 'Coming up...'
if (anyUnhealthy) return 'Unhealthy'
return 'Healthy'
}
return {
services,
isLoadingChecks,
allServicesOperational,
isProjectNew,
isProjectComingUp,
overallStatusLabel: getOverallStatusLabel(),
}
}