@ -1,5 +1,5 @@
import { map , m ergeMap , throttleTime } from 'rxjs/operators' ;
import { identity , Unsubscribable } from 'rxjs' ;
import { mergeMap , throttleTime } from 'rxjs/operators' ;
import { identity , Unsubscribable , of } from 'rxjs' ;
import {
DataQuery ,
DataQueryErrorType ,
@ -27,19 +27,14 @@ import { ExploreId, QueryOptions } from 'app/types/explore';
import { getTimeZone } from 'app/features/profile/state/selectors' ;
import { getShiftedTimeRange } from 'app/core/utils/timePicker' ;
import { notifyApp } from '../../../core/actions' ;
import { preProcessPanelData , runRequest } from '../../query/state/runRequest' ;
import {
decorateWithFrameTypeMetadata ,
decorateWithGraphResult ,
decorateWithLogsResult ,
decorateWithTableResult ,
} from '../utils/decorators' ;
import { runRequest } from '../../query/state/runRequest' ;
import { decorateData } from '../utils/decorators' ;
import { createErrorNotification } from '../../../core/copy/appNotification' ;
import { richHistoryUpdatedAction , stateSave } from './main' ;
import { AnyAction , createAction , PayloadAction } from '@reduxjs/toolkit' ;
import { updateTime } from './time' ;
import { historyUpdatedAction } from './history' ;
import { createEmptyQueryResponse } from './utils' ;
import { createEmptyQueryResponse , createCacheKey , getResultsFromCache } from './utils' ;
//
// Actions and Payloads
@ -164,6 +159,24 @@ export interface ScanStopPayload {
}
export const scanStopAction = createAction < ScanStopPayload > ( 'explore/scanStop' ) ;
/ * *
* Adds query results to cache .
* This is currently used to cache last 5 query results for log queries run from logs navigation ( pagination ) .
* /
export interface AddResultsToCachePayload {
exploreId : ExploreId ;
cacheKey : string ;
queryResponse : PanelData ;
}
export const addResultsToCacheAction = createAction < AddResultsToCachePayload > ( 'explore/addResultsToCache' ) ;
/ * *
* Clears cache .
* /
export interface ClearCachePayload {
exploreId : ExploreId ;
}
export const clearCacheAction = createAction < ClearCachePayload > ( 'explore/clearCache' ) ;
//
// Action creators
//
@ -309,8 +322,26 @@ export const runQueries = (exploreId: ExploreId, options?: { replaceUrl?: boolea
history ,
refreshInterval ,
absoluteRange ,
cache ,
} = exploreItemState ;
let newQuerySub ;
const cachedValue = getResultsFromCache ( cache , absoluteRange ) ;
// If we have results saved in cache, we are going to use those results instead of running queries
if ( cachedValue ) {
newQuerySub = of ( cachedValue )
. pipe ( mergeMap ( ( data : PanelData ) = > decorateData ( data , queryResponse , absoluteRange , refreshInterval , queries ) ) )
. subscribe ( ( data ) = > {
if ( ! data . error ) {
dispatch ( stateSave ( ) ) ;
}
dispatch ( queryStreamUpdatedAction ( { exploreId , response : data } ) ) ;
} ) ;
// If we don't have resuls saved in cache, run new queries
} else {
if ( ! hasNonEmptyQuery ( queries ) ) {
dispatch ( clearQueriesAction ( { exploreId } ) ) ;
dispatch ( stateSave ( { replace : options?.replaceUrl } ) ) ; // Remember to save to state and update location
@ -348,17 +379,13 @@ export const runQueries = (exploreId: ExploreId, options?: { replaceUrl?: boolea
let firstResponse = true ;
dispatch ( changeLoadingStateAction ( { exploreId , loadingState : LoadingState.Loading } ) ) ;
const newQuerySub = runRequest ( datasourceInstance , transaction . request )
newQuerySub = runRequest ( datasourceInstance , transaction . request )
. pipe (
// Simple throttle for live tailing, in case of > 1000 rows per interval we spend about 200ms on processing and
// rendering. In case this is optimized this can be tweaked, but also it should be only as fast as user
// actually can see what is happening.
live ? throttleTime ( 500 ) : identity ,
map ( ( data : PanelData ) = > preProcessPanelData ( data , queryResponse ) ) ,
map ( decorateWithFrameTypeMetadata ) ,
map ( decorateWithGraphResult ) ,
map ( decorateWithLogsResult ( { absoluteRange , refreshInterval , queries } ) ) ,
mergeMap ( decorateWithTableResult )
mergeMap ( ( data : PanelData ) = > decorateData ( data , queryResponse , absoluteRange , refreshInterval , queries ) )
)
. subscribe (
( data ) = > {
@ -403,6 +430,7 @@ export const runQueries = (exploreId: ExploreId, options?: { replaceUrl?: boolea
console . error ( error ) ;
}
) ;
}
dispatch ( queryStoreSubscriptionAction ( { exploreId , querySubscription : newQuerySub } ) ) ;
} ;
@ -439,6 +467,25 @@ export function scanStart(exploreId: ExploreId): ThunkResult<void> {
} ;
}
export function addResultsToCache ( exploreId : ExploreId ) : ThunkResult < void > {
return ( dispatch , getState ) = > {
const queryResponse = getState ( ) . explore [ exploreId ] ! . queryResponse ;
const absoluteRange = getState ( ) . explore [ exploreId ] ! . absoluteRange ;
const cacheKey = createCacheKey ( absoluteRange ) ;
// Save results to cache only when all results recived and loading is done
if ( queryResponse . state === LoadingState . Done ) {
dispatch ( addResultsToCacheAction ( { exploreId , cacheKey , queryResponse } ) ) ;
}
} ;
}
export function clearCache ( exploreId : ExploreId ) : ThunkResult < void > {
return ( dispatch , getState ) = > {
dispatch ( clearCacheAction ( { exploreId } ) ) ;
} ;
}
//
// Reducer
//
@ -629,6 +676,32 @@ export const queryReducer = (state: ExploreItemState, action: AnyAction): Explor
} ;
}
if ( addResultsToCacheAction . match ( action ) ) {
const CACHE_LIMIT = 5 ;
const { cache } = state ;
const { queryResponse , cacheKey } = action . payload ;
let newCache = [ . . . cache ] ;
const isDuplicateKey = newCache . some ( ( c ) = > c . key === cacheKey ) ;
if ( ! isDuplicateKey ) {
const newCacheItem = { key : cacheKey , value : queryResponse } ;
newCache = [ newCacheItem , . . . newCache ] . slice ( 0 , CACHE_LIMIT ) ;
}
return {
. . . state ,
cache : newCache ,
} ;
}
if ( clearCacheAction . match ( action ) ) {
return {
. . . state ,
cache : [ ] ,
} ;
}
return state ;
} ;