1
- import { useCallback , useEffect , useMemo } from 'react' ;
1
+ import { useCallback , useEffect , useMemo , useState } from 'react' ;
2
2
import { logger } from '@sentry/react' ;
3
3
4
4
import { type ApiResult } from 'sentry/api' ;
5
+ import { defined } from 'sentry/utils' ;
5
6
import { encodeSort , type EventsMetaType } from 'sentry/utils/discover/eventView' ;
6
7
import type { Sort } from 'sentry/utils/discover/fields' ;
7
8
import { DiscoverDatasets } from 'sentry/utils/discover/types' ;
@@ -23,6 +24,7 @@ import {useTraceItemDetails} from 'sentry/views/explore/hooks/useTraceItemDetail
23
24
import {
24
25
AlwaysPresentLogFields ,
25
26
MAX_LOG_INGEST_DELAY ,
27
+ MINIMUM_INFINITE_SCROLL_FETCH_COOLDOWN_MS ,
26
28
QUERY_PAGE_LIMIT ,
27
29
QUERY_PAGE_LIMIT_WITH_AUTO_REFRESH ,
28
30
} from 'sentry/views/explore/logs/constants' ;
@@ -211,9 +213,6 @@ function useLogsQueryKey({
211
213
}
212
214
213
215
const orderby = eventViewPayload . sort ;
214
- // we can only turn on high accuracy flex time sampling when
215
- // the order by is exactly timestamp descending,
216
- highFidelity = highFidelity && orderby === '-timestamp' ;
217
216
218
217
const params = {
219
218
query : {
@@ -285,12 +284,39 @@ function getPageParam(
285
284
pageParam : LogPageParam
286
285
) : LogPageParam => {
287
286
const [ pageData , _statusText , response ] = result ;
288
-
289
287
const sortBy = getTimeBasedSortBy ( sortBys ) ;
288
+
290
289
if ( ! sortBy ) {
291
290
// Only sort by timestamp precise is supported for infinite queries.
292
291
return null ;
293
292
}
293
+
294
+ const isDescending = sortBy . kind === 'desc' ;
295
+ // Previous pages have to have the sort order reversed in order to start at the limit from the initial page.
296
+ const querySortDirection : Sort | undefined = isGetPreviousPage
297
+ ? {
298
+ field : OurLogKnownFieldKey . TIMESTAMP ,
299
+ kind : isDescending ? 'asc' : 'desc' ,
300
+ }
301
+ : undefined ;
302
+
303
+ if ( highFidelity || isFlexTimePageParam ( pageParam ) ) {
304
+ const pageLinkHeader = response ?. getResponseHeader ( 'Link' ) ?? null ;
305
+ const links = parseLinkHeader ( pageLinkHeader ) ;
306
+ const link = isGetPreviousPage ? links . previous : links . next ;
307
+
308
+ if ( ! link ?. results ) {
309
+ return undefined ;
310
+ }
311
+
312
+ return {
313
+ querySortDirection,
314
+ sortByDirection : sortBy . kind ,
315
+ autoRefresh,
316
+ cursor : link . cursor ?? undefined ,
317
+ } as FlexTimePageParam ;
318
+ }
319
+
294
320
const firstRow = pageData . data ?. [ 0 ] ;
295
321
const lastRow = pageData . data ?. [ pageData . data . length - 1 ] ;
296
322
if ( ! firstRow || ! lastRow ) {
@@ -319,45 +345,19 @@ function getPageParam(
319
345
const logId = isGetPreviousPage
320
346
? firstRow [ OurLogKnownFieldKey . ID ]
321
347
: lastRow [ OurLogKnownFieldKey . ID ] ;
322
- const isDescending = sortBy . kind === 'desc' ;
323
348
const timestampPrecise = isGetPreviousPage ? firstTimestamp : lastTimestamp ;
324
- let querySortDirection : Sort | undefined = undefined ;
325
- const reverseSortDirection = isDescending ? 'asc' : 'desc' ;
326
-
327
- if ( isGetPreviousPage ) {
328
- // Previous pages have to have the sort order reversed in order to start at the limit from the initial page.
329
- querySortDirection = {
330
- field : OurLogKnownFieldKey . TIMESTAMP ,
331
- kind : reverseSortDirection ,
332
- } ;
333
- }
334
349
335
350
const indexFromInitialPage = isGetPreviousPage
336
351
? ( pageParam ?. indexFromInitialPage ?? 0 ) - 1
337
352
: ( pageParam ?. indexFromInitialPage ?? 0 ) + 1 ;
338
353
339
- let cursor : PageParam [ 'cursor' ] = undefined ;
340
- if ( isDescending && ! autoRefresh && highFidelity ) {
341
- const pageLinkHeader = response ?. getResponseHeader ( 'Link' ) ?? null ;
342
- const links = parseLinkHeader ( pageLinkHeader ) ;
343
- // the flex time sampling strategy has no previous page cursor and only
344
- // a next page cursor because it only works in timestamp desc order
345
- const link = isGetPreviousPage ? links . previous : links . next ;
346
- // return `undefined` to indicate that there are no results in this direction
347
- if ( ! link ?. results ) {
348
- return undefined ;
349
- }
350
- cursor = link . cursor ?? undefined ;
351
- }
352
-
353
- const pageParamResult : LogPageParam = {
354
+ const pageParamResult : InfiniteScrollPageParam = {
354
355
logId,
355
356
timestampPrecise,
356
357
querySortDirection,
357
358
sortByDirection : sortBy . kind ,
358
359
indexFromInitialPage,
359
360
autoRefresh,
360
- cursor,
361
361
} ;
362
362
363
363
return pageParamResult ;
@@ -417,7 +417,7 @@ function getParamBasedQuery(
417
417
return query ;
418
418
}
419
419
420
- if ( pageParam . cursor ) {
420
+ if ( isFlexTimePageParam ( pageParam ) ) {
421
421
return {
422
422
...query ,
423
423
cursor : pageParam . cursor ,
@@ -448,22 +448,32 @@ function getParamBasedQuery(
448
448
} ;
449
449
}
450
450
451
- interface PageParam {
451
+ interface BaseLogsPageParams {
452
452
// Whether the page param is for auto refresh mode.
453
453
autoRefresh : boolean ;
454
+ // The original sort direction of the query.
455
+ sortByDirection : Sort [ 'kind' ] ;
456
+ // When scrolling is happening towards current time, or during auto refresh, we flip the sort direction passed to the query to get X more rows in the future starting from the last seen row.
457
+ querySortDirection ?: Sort ;
458
+ }
459
+
460
+ interface FlexTimePageParam extends BaseLogsPageParams {
461
+ cursor : string | undefined ;
462
+ }
463
+
464
+ interface InfiniteScrollPageParam extends BaseLogsPageParams {
454
465
// The index of the page from the initial page. Useful for debugging and testing.
455
466
indexFromInitialPage : number ;
456
467
// The id of the log row matching timestampPrecise. We use this to exclude the row from the query to avoid duplicates right on the nanosecond boundary.
457
468
logId : string ;
458
- // The original sort direction of the query.
459
- sortByDirection : Sort [ 'kind' ] ;
460
469
timestampPrecise : bigint | null ;
461
- cursor ?: string ;
462
- // When scrolling is happening towards current time, or during auto refresh, we flip the sort direction passed to the query to get X more rows in the future starting from the last seen row.
463
- querySortDirection ?: Sort ;
464
470
}
465
471
466
- export type LogPageParam = PageParam | null | undefined ;
472
+ export type LogPageParam = FlexTimePageParam | InfiniteScrollPageParam | null | undefined ;
473
+
474
+ function isFlexTimePageParam ( pageParam : LogPageParam ) : pageParam is FlexTimePageParam {
475
+ return defined ( pageParam ) && 'cursor' in pageParam ;
476
+ }
467
477
468
478
type QueryKey = [ url : string , endpointOptions : QueryKeyEndpointOptions , 'infinite' ] ;
469
479
@@ -579,6 +589,12 @@ export function useInfiniteLogsQuery({
579
589
queryClient . setQueryData (
580
590
queryKeyWithInfinite ,
581
591
( oldData : InfiniteData < ApiResult < EventsLogsResult > > | undefined ) => {
592
+ if ( highFidelity ) {
593
+ // TODO: properly remove empty pages in high fidelity mode
594
+ // to avoid reaching max pages
595
+ return oldData ;
596
+ }
597
+
582
598
if ( ! oldData ) {
583
599
return oldData ;
584
600
}
@@ -601,7 +617,7 @@ export function useInfiniteLogsQuery({
601
617
} ;
602
618
}
603
619
) ;
604
- } , [ queryClient , queryKeyWithInfinite , sortBys ] ) ;
620
+ } , [ highFidelity , queryClient , queryKeyWithInfinite , sortBys ] ) ;
605
621
606
622
const { virtualStreamedTimestamp} = useVirtualStreaming ( { data, highFidelity} ) ;
607
623
@@ -661,29 +677,76 @@ export function useInfiniteLogsQuery({
661
677
[ hasNextPage , fetchNextPage , isFetching , isError , nextPageHasData ]
662
678
) ;
663
679
680
+ const lastPageLength = data ?. pages ?. [ data . pages . length - 1 ] ?. [ 0 ] ?. data ?. length ?? 0 ;
681
+ const limit = autoRefresh ? QUERY_PAGE_LIMIT_WITH_AUTO_REFRESH : QUERY_PAGE_LIMIT ;
682
+ const shouldAutoFetchNextPage =
683
+ ! ! highFidelity &&
684
+ hasNextPage &&
685
+ nextPageHasData &&
686
+ ( lastPageLength === 0 || _data . length < limit ) ;
687
+ const [ waitingToAutoFetch , setWaitingToAutoFetch ] = useState < boolean > ( false ) ;
688
+
689
+ useEffect ( ( ) => {
690
+ if ( ! shouldAutoFetchNextPage ) {
691
+ return ( ) => { } ;
692
+ }
693
+
694
+ setWaitingToAutoFetch ( true ) ;
695
+
696
+ const timeoutID = setTimeout ( ( ) => {
697
+ setWaitingToAutoFetch ( false ) ;
698
+ _fetchNextPage ( ) ;
699
+ } , MINIMUM_INFINITE_SCROLL_FETCH_COOLDOWN_MS ) ;
700
+
701
+ return ( ) => clearTimeout ( timeoutID ) ;
702
+ } , [ shouldAutoFetchNextPage , _fetchNextPage ] ) ;
703
+
664
704
return {
665
705
error,
666
706
isError,
667
707
isFetching,
668
- isPending : queryResult . isPending ,
708
+ isPending :
709
+ // query is still pending
710
+ queryResult . isPending ||
711
+ // query finished but we're waiting to auto fetch the next page
712
+ ( waitingToAutoFetch && _data . length === 0 ) ||
713
+ // started auto fetching the next page
714
+ ( shouldAutoFetchNextPage && _data . length === 0 && isFetchingNextPage ) ,
669
715
data : _data ,
670
716
meta : _meta ,
671
717
isRefetching : queryResult . isRefetching ,
672
718
isEmpty :
673
719
! queryResult . isPending &&
674
720
! queryResult . isRefetching &&
675
721
! isError &&
676
- _data . length === 0 ,
722
+ _data . length === 0 &&
723
+ ! shouldAutoFetchNextPage ,
677
724
fetchNextPage : _fetchNextPage ,
678
725
fetchPreviousPage : _fetchPreviousPage ,
679
726
refetch,
680
727
hasNextPage,
681
728
queryKey : queryKeyWithInfinite ,
682
729
hasPreviousPage,
683
- isFetchingNextPage,
730
+ isFetchingNextPage :
731
+ ! shouldAutoFetchNextPage && _data . length > 0 && isFetchingNextPage ,
684
732
isFetchingPreviousPage,
685
- lastPageLength : data ?. pages ?. [ data . pages . length - 1 ] ?. [ 0 ] ?. data ?. length ?? 0 ,
733
+ lastPageLength,
686
734
} ;
687
735
}
688
736
689
737
export type UseInfiniteLogsQueryResult = ReturnType < typeof useInfiniteLogsQuery > ;
738
+
739
+ export function useLogsQueryHighFidelity ( ) {
740
+ const organization = useOrganization ( ) ;
741
+ const sortBys = useQueryParamsSortBys ( ) ;
742
+ const highFidelity = organization . features . includes ( 'ourlogs-high-fidelity' ) ;
743
+
744
+ // we can only turn on high accuracy flex time sampling when
745
+ // the order by is exactly timestamp descending,
746
+ return (
747
+ highFidelity &&
748
+ sortBys . length === 1 &&
749
+ sortBys [ 0 ] ?. field === 'timestamp' &&
750
+ sortBys [ 0 ] ?. kind === 'desc'
751
+ ) ;
752
+ }
0 commit comments