mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 15:19:08 +03:00
handle state checking project events
This commit is contained in:
1 parent
608c76a176
commit
c6103e764d
4 files changed
+92
-34
No files matched your search
@@ -82,11 +82,11 @@ export const SecurityStatus = () => {
|
||||
return (
|
||||
lints.length > 0 && (
|
||||
<>
|
||||
<Link href={`/project/${ref}/database/security-advisor?preset=${level}`}>
|
||||
<div
|
||||
key={level}
|
||||
className="group flex items-center justify-between w-full px-3 py-3 transition hover:bg-surface-300"
|
||||
>
|
||||
<Link
|
||||
key={level}
|
||||
href={`/project/${ref}/database/security-advisor?preset=${level}`}
|
||||
>
|
||||
<div className="group flex items-center justify-between w-full px-3 py-3 transition hover:bg-surface-300">
|
||||
<div className="flex gap-x-3">
|
||||
<div>
|
||||
<StatusDot level={level} />
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
Popover_Shadcn_,
|
||||
} from 'ui'
|
||||
import { PopoverSeparator } from '@ui/components/shadcn/ui/popover'
|
||||
import { useProjectEventsQuery } from 'data/project-events/project-events-query'
|
||||
|
||||
const SERVICE_STATUS_THRESHOLD = 5 // minutes
|
||||
|
||||
@@ -30,7 +31,7 @@ const StatusMessage = ({
|
||||
isProjectNew: boolean
|
||||
}) => {
|
||||
if (isLoading) return 'Checking status'
|
||||
if (isProjectNew) return 'Coming up...'
|
||||
if (isProjectNew && !isSuccess) return 'Coming up...'
|
||||
if (isSuccess) return 'No issues'
|
||||
return 'Unable to connect'
|
||||
}
|
||||
@@ -44,7 +45,9 @@ const StatusIcon = ({
|
||||
isSuccess: boolean
|
||||
isProjectNew: boolean
|
||||
}) => {
|
||||
if (isLoading || isProjectNew) return <Loader2 size={14} className="animate-spin" />
|
||||
if (isLoading) return <Loader2 size={14} className="animate-spin text-foreground-lighter" />
|
||||
if (isProjectNew && !isSuccess)
|
||||
return <Loader2 size={14} className="animate-spin text-foreground-lighter" />
|
||||
if (isSuccess) return <CheckCircle2 className="text-brand" size={18} strokeWidth={1.5} />
|
||||
return <AlertTriangle className="text-warning" size={18} strokeWidth={1.5} />
|
||||
}
|
||||
@@ -168,40 +171,40 @@ const ServiceStatus = () => {
|
||||
const isLoadingChecks = services.some((service) => service.isLoading)
|
||||
const allServicesOperational = services.every((service) => service.isSuccess)
|
||||
|
||||
// If the project is less than 5 minutes old, and status is not operational, then it's likely the service is still starting up
|
||||
const isProjectNew =
|
||||
const [dateRange] = useState<{ startDate: string; endDate: string }>({
|
||||
startDate: dayjs().subtract(30, 'minute').toISOString(),
|
||||
endDate: dayjs().toISOString(),
|
||||
})
|
||||
|
||||
const { data: restoreProjectEvents, isLoading: projectEventsLoading } = useProjectEventsQuery({
|
||||
ref,
|
||||
startDate: dateRange.startDate,
|
||||
endDate: dateRange.endDate,
|
||||
eventTypes: [
|
||||
'project.infra_restart_or_resize_initiated',
|
||||
'project.restore_initiated',
|
||||
'project.restored_from_logical_backup',
|
||||
'project.restored_from_pitr',
|
||||
],
|
||||
})
|
||||
|
||||
console.log('events', restoreProjectEvents)
|
||||
|
||||
const projectWasRestoredRecently = !projectEventsLoading && !!restoreProjectEvents?.length
|
||||
|
||||
// If the project is less than SERVICE_STATUS_THRESHOLD minutes old, and status is not operational, then it's likely the service is still starting up
|
||||
const isProjectNewOrRestoredRecently =
|
||||
projectWasRestoredRecently ||
|
||||
dayjs.utc().diff(dayjs.utc(project?.inserted_at), 'minute') < SERVICE_STATUS_THRESHOLD ||
|
||||
project?.status === 'COMING_UP'
|
||||
|
||||
useEffect(() => {
|
||||
let timer: any
|
||||
|
||||
if (isProjectNew) {
|
||||
const secondsSinceProjectCreated = dayjs
|
||||
.utc()
|
||||
.diff(dayjs.utc(project?.inserted_at), 'seconds')
|
||||
const remainingTimeTillNextCheck = SERVICE_STATUS_THRESHOLD * 60 - secondsSinceProjectCreated
|
||||
|
||||
timer = setTimeout(() => {
|
||||
refetchServiceStatus()
|
||||
refetchPostgresServiceStatus()
|
||||
refetchEdgeFunctionServiceStatus()
|
||||
}, remainingTimeTillNextCheck * 1000)
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isProjectNew])
|
||||
|
||||
return (
|
||||
<Popover_Shadcn_ modal={false} open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger_Shadcn_ asChild>
|
||||
<Button
|
||||
type="default"
|
||||
icon={
|
||||
isLoadingChecks || isProjectNew ? (
|
||||
isLoadingChecks || projectEventsLoading ? (
|
||||
<Loader2 className="animate-spin" />
|
||||
) : (
|
||||
<div
|
||||
@@ -226,7 +229,7 @@ const ServiceStatus = () => {
|
||||
<StatusIcon
|
||||
isLoading={service.isLoading}
|
||||
isSuccess={!!service.isSuccess}
|
||||
isProjectNew={isProjectNew}
|
||||
isProjectNew={isProjectNewOrRestoredRecently}
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<p>{service.name}</p>
|
||||
@@ -234,7 +237,7 @@ const ServiceStatus = () => {
|
||||
<StatusMessage
|
||||
isLoading={service.isLoading}
|
||||
isSuccess={!!service.isSuccess}
|
||||
isProjectNew={isProjectNew}
|
||||
isProjectNew={isProjectNewOrRestoredRecently}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
export const projectEventsKeys = {
|
||||
list: (
|
||||
ref?: string,
|
||||
startDate?: string,
|
||||
endDate?: string,
|
||||
eventTypes?: string[],
|
||||
limit?: number
|
||||
) => ['projects', ref, 'events', startDate, endDate, eventTypes, limit] as const,
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
|
||||
import type { components } from 'data/api'
|
||||
import { get, handleError } from 'data/fetchers'
|
||||
import type { ResponseError } from 'types'
|
||||
import { projectEventsKeys } from './keys'
|
||||
|
||||
export type ProjectEventsVariables = {
|
||||
ref?: string
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
eventTypes?: string[]
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export type ProjectSecret = components['schemas']['SecretResponse']
|
||||
|
||||
export async function getProjectEvents(
|
||||
{ ref, startDate, endDate, eventTypes, limit }: ProjectEventsVariables,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!ref) throw new Error('Project ref is required')
|
||||
|
||||
const { data, error } = await get(`/v1/projects/{ref}/events`, {
|
||||
params: { path: { ref }, query: { startDate, endDate, eventTypes, limit } },
|
||||
signal,
|
||||
})
|
||||
|
||||
if (error) handleError(error)
|
||||
|
||||
// TODO: undo type cast when API types are updated
|
||||
return data as { event_type: string; inserted_at: string }[]
|
||||
}
|
||||
|
||||
export type ProjectEventsData = Awaited<ReturnType<typeof getProjectEvents>>
|
||||
export type ProjectEventsError = ResponseError
|
||||
|
||||
export const useProjectEventsQuery = <TData = ProjectEventsData>(
|
||||
{ ref, startDate, endDate, eventTypes, limit }: ProjectEventsVariables,
|
||||
{ enabled = true, ...options }: UseQueryOptions<ProjectEventsData, ProjectEventsError, TData> = {}
|
||||
) =>
|
||||
useQuery<ProjectEventsData, ProjectEventsError, TData>(
|
||||
projectEventsKeys.list(ref, startDate, endDate, eventTypes, limit),
|
||||
({ signal }) => getProjectEvents({ ref, startDate, endDate, eventTypes, limit }, signal),
|
||||
{ enabled: enabled && typeof ref !== 'undefined', ...options }
|
||||
)
|
||||
Reference in new issue
Block a user