File size: 4,754 Bytes
b2a00c5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | import { useEffect } from 'react'
import {
useQueryClient,
useInfiniteQuery,
QueryFilters
} from '@tanstack/react-query'
import * as api from '../api'
import { DashboardState } from '../dashboard-state'
import {
DashboardPeriod,
DashboardTimeSettings,
isHistoricalPeriod
} from '../dashboard-time-periods'
import { REALTIME_UPDATE_TIME_MS } from '../util/realtime-update-timer'
import { Interval, validIntervals } from '../stats/graph/intervals'
// define (in ms) when query API responses should become stale
export const CACHE_TTL_REALTIME = REALTIME_UPDATE_TIME_MS
export const CACHE_TTL_SHORT_ONGOING = 5 * 60 * 1000 // 5 minutes
export const CACHE_TTL_LONG_ONGOING = 60 * 60 * 1000 // 1 hour
export const CACHE_TTL_HISTORICAL = 12 * 60 * 60 * 1000 // 12 hours
// how many items per page for breakdown modals
export const PAGINATION_LIMIT = 100
/** full endpoint URL */
type Endpoint = string
type PaginatedQueryKeyBase = [Endpoint, { dashboardState: DashboardState }]
type GetRequestParams<TKey extends PaginatedQueryKeyBase> = (
k: TKey
) => [DashboardState, Record<string, unknown>]
/**
* Hook that fetches the first page from the defined GET endpoint on mount,
* then subsequent pages when component calls fetchNextPage.
* Stores fetched pages locally, but only the first page of the results.
*/
export function usePaginatedGetAPI<
TResponse extends { results: unknown[] },
TKey extends PaginatedQueryKeyBase = PaginatedQueryKeyBase
>({
siteTimezoneOffset,
siteStatsBegin,
key,
getRequestParams,
afterFetchData,
afterFetchNextPage,
initialPageParam = 1
}: {
siteTimezoneOffset: DashboardTimeSettings['siteTimezoneOffset']
siteStatsBegin: DashboardTimeSettings['siteStatsBegin']
key: TKey
getRequestParams: GetRequestParams<TKey>
afterFetchData?: (response: TResponse) => void
afterFetchNextPage?: (response: TResponse) => void
initialPageParam?: number
}) {
const [endpoint] = key
const queryClient = useQueryClient()
useEffect(() => {
const onDismountCleanToPageOne = () => {
const queryKeyToClean = [endpoint] as QueryFilters
queryClient.setQueriesData(queryKeyToClean, cleanToPageOne)
}
return onDismountCleanToPageOne
}, [queryClient, endpoint])
return useInfiniteQuery({
queryKey: key,
queryFn: async ({ pageParam, queryKey }): Promise<TResponse['results']> => {
const [dashboardState, params] = getRequestParams(queryKey)
const response: TResponse = await api.get(endpoint, dashboardState, {
...params,
limit: PAGINATION_LIMIT,
page: pageParam
})
if (
pageParam === initialPageParam &&
typeof afterFetchData === 'function'
) {
afterFetchData(response)
}
if (
pageParam > initialPageParam &&
typeof afterFetchNextPage === 'function'
) {
afterFetchNextPage(response)
}
return response.results
},
getNextPageParam: (lastPageResults, _, lastPageIndex) => {
return lastPageResults.length === PAGINATION_LIMIT
? lastPageIndex + 1
: null
},
staleTime: ({ queryKey }) => {
const [_, opts] = queryKey
return getStaleTime({
siteTimezoneOffset: siteTimezoneOffset,
siteStatsBegin: siteStatsBegin,
...opts.dashboardState
})
},
initialPageParam,
placeholderData: (previousData) => previousData
})
}
export const cleanToPageOne = <
T extends { pages: unknown[]; pageParams: unknown[] }
>(
data?: T
) => {
if (data?.pages?.length) {
return {
pages: data.pages.slice(0, 1),
pageParams: data.pageParams.slice(0, 1)
}
}
return data
}
/**
* Returns the time-to-live for cached query API responses based on the given DashboardTimeSettings.
*
* - For a realtime dashboard: {@link CACHE_TTL_REALTIME}
* - For any historical period (i.e. does not include today): {@link CACHE_TTL_HISTORICAL}
* - For a period that includes today, supporting 'day' or shorter interval: {@link CACHE_TTL_SHORT_ONGOING}
* - For a period that includes today, too long to support 'day' interval: {@link CACHE_TTL_LONG_ONGOING}
*/
export const getStaleTime = (props: DashboardTimeSettings): number => {
if (
[DashboardPeriod.realtime, DashboardPeriod.realtime_30m].includes(
props.period
)
) {
return CACHE_TTL_REALTIME
}
if (isHistoricalPeriod(props)) {
return CACHE_TTL_HISTORICAL
}
const availableIntervals = validIntervals(props)
if (
availableIntervals.includes(Interval.day) ||
availableIntervals.includes(Interval.hour) ||
availableIntervals.includes(Interval.minute)
) {
return CACHE_TTL_SHORT_ONGOING
} else {
return CACHE_TTL_LONG_ONGOING
}
}
|