CronJobsTab.useCronJobsData.ts151 lines · main
| 1 | import { keepPreviousData } from '@tanstack/react-query' |
| 2 | import { useMemo } from 'react' |
| 3 | |
| 4 | import { CRON_JOBS_THRESHOLD } from './CronJobsTab.constants' |
| 5 | import type { ConnectionVars } from '@/data/common.types' |
| 6 | import { useCronJobsCountEstimateQuery } from '@/data/database-cron-jobs/database-cron-jobs-count-estimate-query' |
| 7 | import { useCronJobsCountQuery } from '@/data/database-cron-jobs/database-cron-jobs-count-query' |
| 8 | import { |
| 9 | CronJob, |
| 10 | useCronJobsInfiniteQuery, |
| 11 | } from '@/data/database-cron-jobs/database-cron-jobs-infinite-query' |
| 12 | import { useCronJobsMinimalInfiniteQuery } from '@/data/database-cron-jobs/database-cron-jobs-minimal-infinite-query' |
| 13 | import { COST_THRESHOLD_ERROR } from '@/data/sql/execute-sql-query' |
| 14 | import type { ResponseError } from '@/types' |
| 15 | |
| 16 | // ============================================================================= |
| 17 | // Input types |
| 18 | // ============================================================================= |
| 19 | |
| 20 | type UseCronJobsDataParams = ConnectionVars & { |
| 21 | searchQuery: string | null |
| 22 | } |
| 23 | |
| 24 | // ============================================================================= |
| 25 | // Shared state types (available in all states) |
| 26 | // ============================================================================= |
| 27 | |
| 28 | interface CronJobsGridState { |
| 29 | rows: Array<CronJob> |
| 30 | queryCost?: number |
| 31 | isSuccess: boolean |
| 32 | isLoading: boolean |
| 33 | error: ResponseError | null |
| 34 | isRefetching: boolean |
| 35 | isFetchingNextPage: boolean |
| 36 | hasNextPage: boolean |
| 37 | isMinimal: boolean |
| 38 | refetch: () => void |
| 39 | fetchNextPage: () => void |
| 40 | } |
| 41 | |
| 42 | interface CronJobsCountState { |
| 43 | value: number | undefined |
| 44 | isEstimate: boolean |
| 45 | isLoading: boolean |
| 46 | } |
| 47 | |
| 48 | // ============================================================================= |
| 49 | // Result type |
| 50 | // ============================================================================= |
| 51 | |
| 52 | interface UseCronJobsDataResult { |
| 53 | /** State for the cron jobs grid */ |
| 54 | grid: CronJobsGridState |
| 55 | /** State for the cron jobs count (exact or estimate) */ |
| 56 | count: CronJobsCountState |
| 57 | } |
| 58 | export function useCronJobsData({ |
| 59 | projectRef, |
| 60 | connectionString, |
| 61 | searchQuery, |
| 62 | }: UseCronJobsDataParams): UseCronJobsDataResult { |
| 63 | const isProjectReady = !!projectRef |
| 64 | |
| 65 | const { |
| 66 | data: cronJobsData, |
| 67 | error: cronJobsError, |
| 68 | isSuccess: isCronJobsSuccess, |
| 69 | isLoading: isCronJobsLoading, |
| 70 | isRefetching: isCronJobsRefetching, |
| 71 | isFetchingNextPage, |
| 72 | hasNextPage = false, |
| 73 | refetch: refetchCronJobs, |
| 74 | fetchNextPage, |
| 75 | } = useCronJobsInfiniteQuery( |
| 76 | { projectRef, connectionString, searchTerm: searchQuery ?? undefined }, |
| 77 | { |
| 78 | placeholderData: Boolean(searchQuery) ? keepPreviousData : undefined, |
| 79 | staleTime: Infinity, |
| 80 | } |
| 81 | ) |
| 82 | const useMinimalQuery = cronJobsError?.message === COST_THRESHOLD_ERROR |
| 83 | |
| 84 | const { |
| 85 | data: cronJobsMinimalData, |
| 86 | error: cronJobsMinimalError, |
| 87 | isSuccess: isCronJobsMinimalSuccess, |
| 88 | isLoading: isCronJobsMinimalLoading, |
| 89 | isRefetching: isCronJobsMinimalRefetching, |
| 90 | isFetchingNextPage: isFetchingNextPageMinimal, |
| 91 | hasNextPage: hasNextPageMinimal = false, |
| 92 | refetch: refetchCronJobsMinimal, |
| 93 | fetchNextPage: fetchNextPageMinimal, |
| 94 | } = useCronJobsMinimalInfiniteQuery( |
| 95 | { projectRef, connectionString, searchTerm: searchQuery ?? undefined }, |
| 96 | { |
| 97 | placeholderData: Boolean(searchQuery) ? keepPreviousData : undefined, |
| 98 | staleTime: Infinity, |
| 99 | enabled: useMinimalQuery, |
| 100 | } |
| 101 | ) |
| 102 | |
| 103 | const cronJobs = useMemo(() => { |
| 104 | if (useMinimalQuery) { |
| 105 | return cronJobsMinimalData?.pages.flatMap((page) => page) ?? [] |
| 106 | } else { |
| 107 | return cronJobsData?.pages.flatMap((page) => page) ?? [] |
| 108 | } |
| 109 | }, [useMinimalQuery, cronJobsData?.pages, cronJobsMinimalData?.pages]) |
| 110 | |
| 111 | // Fetch count - gated on cron.job table size |
| 112 | // Always fetch the estimate first (it's fast since it uses pg_stat) |
| 113 | const { |
| 114 | data: estimatedCount, |
| 115 | isPending: isLoadingEstimatedCount, |
| 116 | isError: isEstimatedCountError, |
| 117 | } = useCronJobsCountEstimateQuery({ projectRef, connectionString }, { enabled: isProjectReady }) |
| 118 | |
| 119 | const hasLargeCronJobsTable = |
| 120 | typeof estimatedCount === 'number' && estimatedCount > CRON_JOBS_THRESHOLD |
| 121 | |
| 122 | // Exact count is enabled when the cron.job table is small enough |
| 123 | const isCountQueryEnabled = |
| 124 | isProjectReady && !isLoadingEstimatedCount && !isEstimatedCountError && !hasLargeCronJobsTable |
| 125 | |
| 126 | const { data: exactCount, isPending: isLoadingExactCount } = useCronJobsCountQuery( |
| 127 | { projectRef, connectionString }, |
| 128 | { enabled: isCountQueryEnabled } |
| 129 | ) |
| 130 | |
| 131 | return { |
| 132 | grid: { |
| 133 | rows: cronJobs, |
| 134 | queryCost: cronJobsError?.metadata?.cost, |
| 135 | error: useMinimalQuery ? cronJobsMinimalError : cronJobsError, |
| 136 | isSuccess: useMinimalQuery ? isCronJobsMinimalSuccess : isCronJobsSuccess, |
| 137 | isLoading: useMinimalQuery ? isCronJobsMinimalLoading : isCronJobsLoading, |
| 138 | isRefetching: useMinimalQuery ? isCronJobsMinimalRefetching : isCronJobsRefetching, |
| 139 | isFetchingNextPage: useMinimalQuery ? isFetchingNextPageMinimal : isFetchingNextPage, |
| 140 | hasNextPage: useMinimalQuery ? hasNextPageMinimal : hasNextPage, |
| 141 | isMinimal: useMinimalQuery, |
| 142 | refetch: useMinimalQuery ? refetchCronJobsMinimal : refetchCronJobs, |
| 143 | fetchNextPage: useMinimalQuery ? fetchNextPageMinimal : fetchNextPage, |
| 144 | }, |
| 145 | count: { |
| 146 | value: isCountQueryEnabled ? exactCount : estimatedCount, |
| 147 | isEstimate: !isCountQueryEnabled, |
| 148 | isLoading: isCountQueryEnabled ? isLoadingExactCount : isLoadingEstimatedCount, |
| 149 | }, |
| 150 | } |
| 151 | } |