@@ -33,18 +33,122 @@ function queryKeyFn(
3333 return [ prefix , config , queryTimeout ] ;
3434}
3535
36- type TPageParam = number ;
36+ // Time window configuration - progressive bucketing strategy
37+ const TIME_WINDOWS_MS = [
38+ 6 * 60 * 60 * 1000 , // 6h
39+ 6 * 60 * 60 * 1000 , // 6h
40+ 12 * 60 * 60 * 1000 , // 12h
41+ 24 * 60 * 60 * 1000 , // 24h
42+ ] ;
43+
44+ type TimeWindow = {
45+ startTime : Date ;
46+ endTime : Date ;
47+ windowIndex : number ;
48+ } ;
49+
50+ type TPageParam = {
51+ windowIndex : number ;
52+ offset : number ;
53+ } ;
54+
3755type TQueryFnData = {
3856 data : Record < string , any > [ ] ;
3957 meta : ColumnMetaType [ ] ;
4058 chSql : ChSql ;
59+ window : TimeWindow ;
4160} ;
61+
4262type TData = {
4363 pages : TQueryFnData [ ] ;
4464 pageParams : TPageParam [ ] ;
4565} ;
4666
47- const queryFn : QueryFunction < TQueryFnData , TQueryKey , number > = async ( {
67+ // Generate time windows from date range using progressive bucketing
68+ function generateTimeWindows ( startDate : Date , endDate : Date ) : TimeWindow [ ] {
69+ const windows : TimeWindow [ ] = [ ] ;
70+ let currentEnd = new Date ( endDate ) ;
71+ let windowIndex = 0 ;
72+
73+ while ( currentEnd > startDate ) {
74+ const windowSize =
75+ TIME_WINDOWS_MS [ windowIndex ] ||
76+ TIME_WINDOWS_MS [ TIME_WINDOWS_MS . length - 1 ] ; // use largest window size
77+ const windowStart = new Date (
78+ Math . max ( currentEnd . getTime ( ) - windowSize , startDate . getTime ( ) ) ,
79+ ) ;
80+
81+ windows . push ( {
82+ endTime : new Date ( currentEnd ) ,
83+ startTime : windowStart ,
84+ windowIndex,
85+ } ) ;
86+
87+ currentEnd = windowStart ;
88+ windowIndex ++ ;
89+ }
90+
91+ return windows ;
92+ }
93+
94+ // Get time window from page param
95+ function getTimeWindowFromPageParam (
96+ config : ChartConfigWithDateRange ,
97+ pageParam : TPageParam ,
98+ ) : TimeWindow {
99+ const [ startDate , endDate ] = config . dateRange ;
100+ const windows = generateTimeWindows ( startDate , endDate ) ;
101+ const window = windows [ pageParam . windowIndex ] ;
102+ if ( window == null ) {
103+ throw new Error ( 'Invalid time window for page param' ) ;
104+ }
105+ return window ;
106+ }
107+
108+ // Calculate next page param based on current results and window
109+ function getNextPageParam (
110+ lastPage : TQueryFnData | null ,
111+ allPages : TQueryFnData [ ] ,
112+ config : ChartConfigWithDateRange ,
113+ ) : TPageParam | undefined {
114+ if ( lastPage == null ) {
115+ return undefined ;
116+ }
117+
118+ const [ startDate , endDate ] = config . dateRange ;
119+ const windows = generateTimeWindows ( startDate , endDate ) ;
120+ const currentWindow = lastPage . window ;
121+
122+ // Calculate total results from all pages in current window
123+ const currentWindowPages = allPages . filter (
124+ p => p . window . windowIndex === currentWindow . windowIndex ,
125+ ) ;
126+ const currentWindowResults = currentWindowPages . reduce (
127+ ( sum , page ) => sum + page . data . length ,
128+ 0 ,
129+ ) ;
130+
131+ // If we have results in the current window, continue paginating within it
132+ if ( lastPage . data . length > 0 ) {
133+ return {
134+ windowIndex : currentWindow . windowIndex ,
135+ offset : currentWindowResults ,
136+ } ;
137+ }
138+
139+ // If no more results in current window, move to next window
140+ const nextWindowIndex = currentWindow . windowIndex + 1 ;
141+ if ( nextWindowIndex < windows . length ) {
142+ return {
143+ windowIndex : nextWindowIndex ,
144+ offset : 0 ,
145+ } ;
146+ }
147+
148+ return undefined ;
149+ }
150+
151+ const queryFn : QueryFunction < TQueryFnData , TQueryKey , TPageParam > = async ( {
48152 queryKey,
49153 pageParam,
50154 signal,
@@ -57,19 +161,27 @@ const queryFn: QueryFunction<TQueryFnData, TQueryKey, number> = async ({
57161 // Only stream incrementally if this is a fresh query with no previous
58162 // response or if it's a paginated query
59163 // otherwise we'll flicker the UI with streaming data
60- const isStreamingIncrementally = ! meta . hasPreviousQueries || pageParam > 0 ;
164+ const isStreamingIncrementally =
165+ ! meta . hasPreviousQueries ||
166+ pageParam . offset > 0 ||
167+ pageParam . windowIndex > 0 ;
61168
62169 const config = queryKey [ 1 ] ;
63- const query = await renderChartConfig (
64- {
65- ...config ,
66- limit : {
67- limit : config . limit ?. limit ,
68- offset : pageParam ,
69- } ,
170+
171+ // Get the time window for this page
172+ const timeWindow = getTimeWindowFromPageParam ( config , pageParam ) ;
173+
174+ // Create config with windowed date range
175+ const windowedConfig = {
176+ ...config ,
177+ dateRange : [ timeWindow . startTime , timeWindow . endTime ] as [ Date , Date ] ,
178+ limit : {
179+ limit : config . limit ?. limit ,
180+ offset : pageParam . offset ,
70181 } ,
71- getMetadata ( ) ,
72- ) ;
182+ } ;
183+
184+ const query = await renderChartConfig ( windowedConfig , getMetadata ( ) ) ;
73185
74186 const queryTimeout = queryKey [ 2 ] ;
75187 const clickhouseClient = getClickhouseClient ( { queryTimeout } ) ;
@@ -94,6 +206,7 @@ const queryFn: QueryFunction<TQueryFnData, TQueryKey, number> = async ({
94206 data : [ ] ,
95207 meta : [ ] ,
96208 chSql : { sql : '' , params : { } } ,
209+ window : timeWindow ,
97210 } ;
98211 if ( oldData == null ) {
99212 return {
@@ -161,7 +274,14 @@ const queryFn: QueryFunction<TQueryFnData, TQueryKey, number> = async ({
161274 queryClient . setQueryData < TData > ( queryKey , oldData => {
162275 if ( oldData == null ) {
163276 return {
164- pages : [ { data : rowObjs , meta : queryResultMeta , chSql : query } ] ,
277+ pages : [
278+ {
279+ data : rowObjs ,
280+ meta : queryResultMeta ,
281+ chSql : query ,
282+ window : timeWindow ,
283+ } ,
284+ ] ,
165285 pageParams : [ pageParam ] ,
166286 } ;
167287 }
@@ -177,6 +297,7 @@ const queryFn: QueryFunction<TQueryFnData, TQueryKey, number> = async ({
177297 data : [ ...( page . data ?? [ ] ) , ...rowObjs ] ,
178298 meta : queryResultMeta ,
179299 chSql : query ,
300+ window : timeWindow ,
180301 } ,
181302 ] ,
182303 pageParams : oldData . pageParams ,
@@ -215,6 +336,7 @@ const queryFn: QueryFunction<TQueryFnData, TQueryKey, number> = async ({
215336 data : queryResultData ,
216337 meta : queryResultMeta ,
217338 chSql : query ,
339+ window : timeWindow ,
218340 } ;
219341 }
220342
@@ -244,6 +366,7 @@ function flattenData(data: TData | undefined): TQueryFnData | null {
244366 meta : data . pages [ 0 ] . meta ,
245367 data : flattenPages ( data . pages ) ,
246368 chSql : data . pages [ 0 ] . chSql ,
369+ window : data . pages [ data . pages . length - 1 ] . window ,
247370 } ;
248371}
249372
@@ -290,22 +413,9 @@ export default function useOffsetPaginatedQuery(
290413 return isLive ? prev : undefined ;
291414 } ,
292415 enabled,
293- initialPageParam : 0 ,
294- // TODO: Use initialData derived from cache to do a smarter time range fetch
416+ initialPageParam : { windowIndex : 0 , offset : 0 } as TPageParam ,
295417 getNextPageParam : ( lastPage , allPages ) => {
296- if ( lastPage == null ) {
297- return undefined ;
298- }
299-
300- const len = lastPage . data . length ;
301- if ( len === 0 ) {
302- return undefined ;
303- }
304-
305- const data = flattenPages ( allPages ) ;
306-
307- // TODO: Need to configure overlap and account for granularity
308- return data . length ;
418+ return getNextPageParam ( lastPage , allPages , config ) ;
309419 } ,
310420 staleTime : Infinity , // TODO: Pick a correct time
311421 meta : {
0 commit comments