useLogsPreview.tsx 7.2 KB

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