CronJobsTab.useCronJobsData.ts151 lines · main
1import { keepPreviousData } from '@tanstack/react-query'
2import { useMemo } from 'react'
3
4import { CRON_JOBS_THRESHOLD } from './CronJobsTab.constants'
5import type { ConnectionVars } from '@/data/common.types'
6import { useCronJobsCountEstimateQuery } from '@/data/database-cron-jobs/database-cron-jobs-count-estimate-query'
7import { useCronJobsCountQuery } from '@/data/database-cron-jobs/database-cron-jobs-count-query'
8import {
9 CronJob,
10 useCronJobsInfiniteQuery,
11} from '@/data/database-cron-jobs/database-cron-jobs-infinite-query'
12import { useCronJobsMinimalInfiniteQuery } from '@/data/database-cron-jobs/database-cron-jobs-minimal-infinite-query'
13import { COST_THRESHOLD_ERROR } from '@/data/sql/execute-sql-query'
14import type { ResponseError } from '@/types'
15
16// =============================================================================
17// Input types
18// =============================================================================
19
20type UseCronJobsDataParams = ConnectionVars & {
21 searchQuery: string | null
22}
23
24// =============================================================================
25// Shared state types (available in all states)
26// =============================================================================
27
28interface 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
42interface CronJobsCountState {
43 value: number | undefined
44 isEstimate: boolean
45 isLoading: boolean
46}
47
48// =============================================================================
49// Result type
50// =============================================================================
51
52interface 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}
58export 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}