useLogsPreview.tsx275 lines · main
1import { useInfiniteQuery, useQuery } from '@tanstack/react-query'
2import dayjs from 'dayjs'
3import { useCallback, useMemo, useState } from 'react'
4
5import { useFillTimeseriesSorted } from './useFillTimeseriesSorted'
6import { useLogsUrlState } from './useLogsUrlState'
7import useTimeseriesUnixToIso from './useTimeseriesUnixToIso'
8import {
9 getDefaultHelper,
10 LogsTableName,
11 PREVIEWER_DATEPICKER_HELPERS,
12} from '@/components/interfaces/Settings/Logs/Logs.constants'
13import type {
14 Count,
15 EventChart,
16 EventChartData,
17 Filters,
18 LogData,
19 Logs,
20 LogsEndpointParams,
21} from '@/components/interfaces/Settings/Logs/Logs.types'
22import {
23 genChartQuery,
24 genCountQuery,
25 genDefaultQuery,
26} from '@/components/interfaces/Settings/Logs/Logs.utils'
27import { get } from '@/data/fetchers'
28
29interface LogsPreviewHook {
30 logData: LogData[]
31 error: string | Object | null
32 newCount: number
33 isSuccess: boolean
34 isLoading: boolean
35 isLoadingOlder: boolean
36 filters: Filters
37 params: LogsEndpointParams
38 oldestTimestamp?: string
39 eventChartData: EventChartData[]
40 loadOlder: () => void
41 refresh: () => void
42}
43
44function useLogsPreview({
45 projectRef,
46 table,
47 filterOverride,
48 limit,
49}: {
50 projectRef: string
51 table: LogsTableName
52 filterOverride?: Filters
53 limit?: number
54}): LogsPreviewHook {
55 const defaultHelper = getDefaultHelper(PREVIEWER_DATEPICKER_HELPERS)
56 const [latestRefresh, setLatestRefresh] = useState(new Date().toISOString())
57
58 const {
59 timestampStart: urlTimestampStart,
60 timestampEnd: urlTimestampEnd,
61 filters: urlFilters,
62 search,
63 } = useLogsUrlState()
64
65 const timestampStart = useMemo(
66 () => urlTimestampStart || defaultHelper.calcFrom(),
67 [urlTimestampStart, defaultHelper]
68 )
69 const timestampEnd = useMemo(
70 () => urlTimestampEnd || defaultHelper.calcTo(),
71 [urlTimestampEnd, defaultHelper]
72 )
73
74 const mergedFilters = useMemo(
75 () => ({
76 ...urlFilters,
77 ...filterOverride,
78 ...(search ? { search_query: search } : {}),
79 }),
80 [JSON.stringify(urlFilters), JSON.stringify(filterOverride), search]
81 )
82
83 const params: LogsEndpointParams = useMemo(() => {
84 const currentSql = genDefaultQuery(table, mergedFilters, limit)
85 return {
86 iso_timestamp_start: timestampStart,
87 iso_timestamp_end: timestampEnd,
88 sql: currentSql,
89 }
90 }, [timestampStart, timestampEnd, table, mergedFilters, limit])
91
92 const queryKey = useMemo(() => ['projects', projectRef, 'logs', params], [projectRef, params])
93
94 const {
95 data,
96 isSuccess,
97 isLoading,
98 isRefetching,
99 error: rqError,
100 fetchNextPage,
101 isFetchingNextPage,
102 refetch,
103 } = useInfiniteQuery({
104 queryKey,
105 queryFn: async ({ signal, pageParam }) => {
106 const { data, error } = await get(`/platform/projects/{ref}/analytics/endpoints/logs.all`, {
107 params: {
108 path: { ref: projectRef },
109 query: {
110 ...params,
111 iso_timestamp_end: pageParam || params.iso_timestamp_end,
112 },
113 },
114 signal,
115 })
116 if (error) {
117 throw error
118 }
119
120 return data as unknown as Logs
121 },
122 refetchOnWindowFocus: false,
123 initialPageParam: undefined as string | undefined,
124 getNextPageParam(lastPage) {
125 if ((lastPage.result?.length ?? 0) === 0) {
126 return undefined
127 }
128 const len = lastPage.result.length
129 const { timestamp: tsLimit }: LogData = lastPage.result[len - 1]
130 const isoTsLimit = dayjs.utc(Number(tsLimit / 1000)).toISOString()
131 return isoTsLimit
132 },
133 })
134
135 const { logData, error, oldestTimestamp } = useMemo(() => {
136 let logData: LogData[] = []
137 let error: null | string | object = rqError ? (rqError as any).message : null
138
139 data?.pages?.forEach((response) => {
140 if (response?.result) {
141 logData = [...logData, ...response.result]
142 }
143 if (!error && response?.error) {
144 error = response.error
145 }
146 })
147
148 const oldestTimestamp = logData.length > 0 ? logData[logData.length - 1]?.timestamp : undefined
149
150 return { logData, error, oldestTimestamp }
151 }, [data?.pages])
152
153 const countQuerySql = useMemo(() => genCountQuery(table, mergedFilters), [table, mergedFilters])
154 const countQueryKey = useMemo(
155 () => [
156 'projects',
157 projectRef,
158 'logs-count',
159 {
160 projectRef,
161 sql: countQuerySql,
162 iso_timestamp_start: latestRefresh,
163 iso_timestamp_end: timestampEnd,
164 table,
165 mergedFilters,
166 },
167 ],
168 [projectRef, countQuerySql, latestRefresh, timestampEnd, table, mergedFilters]
169 )
170
171 const { data: countData } = useQuery({
172 queryKey: countQueryKey,
173 queryFn: async ({ signal }) => {
174 const { data, error } = await get(`/platform/projects/{ref}/analytics/endpoints/logs.all`, {
175 params: {
176 path: { ref: projectRef },
177 query: {
178 sql: countQuerySql,
179 iso_timestamp_start: latestRefresh,
180 iso_timestamp_end: timestampEnd,
181 },
182 },
183 signal,
184 })
185 if (error) {
186 throw error
187 }
188
189 return data as unknown as Count
190 },
191 refetchOnWindowFocus: false,
192 refetchInterval: 60000,
193 enabled: !error && data && data?.pages?.length > 0 ? true : false,
194 })
195
196 const newCount = countData?.result?.[0]?.count ?? 0
197
198 const chartQuery = useMemo(
199 () => genChartQuery(table, params, mergedFilters),
200 [table, params, mergedFilters]
201 )
202 const chartQueryKey = useMemo(
203 () => [
204 'projects',
205 projectRef,
206 'logs-chart',
207 {
208 projectRef,
209 sql: chartQuery,
210 iso_timestamp_start: timestampStart,
211 iso_timestamp_end: timestampEnd,
212 },
213 ],
214 [projectRef, chartQuery, timestampStart, timestampEnd]
215 )
216
217 const { data: eventChartResponse, refetch: refreshEventChart } = useQuery({
218 queryKey: chartQueryKey,
219 queryFn: async ({ signal }) => {
220 const { data, error } = await get(`/platform/projects/{ref}/analytics/endpoints/logs.all`, {
221 params: {
222 path: { ref: projectRef },
223 query: {
224 iso_timestamp_start: timestampStart,
225 iso_timestamp_end: timestampEnd,
226 sql: chartQuery,
227 },
228 },
229 signal,
230 })
231 if (error) {
232 throw error
233 }
234
235 return data as unknown as EventChart
236 },
237 refetchOnWindowFocus: false,
238 })
239
240 const refresh = useCallback(async () => {
241 setLatestRefresh(new Date().toISOString())
242 refreshEventChart()
243 refetch()
244 }, [refetch, refreshEventChart])
245
246 const normalizedEventChartData = useTimeseriesUnixToIso(
247 eventChartResponse?.result ?? [],
248 'timestamp'
249 )
250
251 const { data: eventChartData, error: eventChartError } = useFillTimeseriesSorted({
252 data: normalizedEventChartData,
253 timestampKey: 'timestamp',
254 valueKey: 'count',
255 defaultValue: 0,
256 startDate: timestampStart,
257 endDate: timestampEnd ?? new Date().toISOString(),
258 })
259
260 return {
261 newCount,
262 logData,
263 isSuccess,
264 isLoading: isLoading || isRefetching,
265 isLoadingOlder: isFetchingNextPage,
266 error: error || eventChartError,
267 filters: mergedFilters,
268 params,
269 oldestTimestamp: oldestTimestamp ? String(oldestTimestamp) : undefined,
270 eventChartData,
271 refresh,
272 loadOlder: () => fetchNextPage(),
273 }
274}
275export default useLogsPreview