import { useCallback , useEffect , useMemo , useReducer , useRef } from 'react' ; import { chunk , debounce } from 'lodash' ; import { IHttpFetchError , ResponseErrorBody } from 'src/core/public' ; import { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames' ; import { EventOutcome } from '../../../../common/event_outcome' ; import { DEBOUNCE_INTERVAL , DEFAULT_PERCENTILE_THRESHOLD , } from '../../../../common/correlations/constants' ; import type { FailedTransactionsCorrelation , FailedTransactionsCorrelationsResponse , } from '../../../../common/correlations/failed_transactions_correlations/types' ; import { callApmApi } from '../../../services/rest/createCallApmApi' ; import { getInitialResponse , getFailedTransactionsCorrelationsSortedByScore , getReducer , CorrelationsProgress , } from './utils/analysis_hook_utils' ; import { useFetchParams } from './use_fetch_params' ; const LOADED_OVERALL_HISTOGRAM = 0.05 ; const LOADED_FIELD_CANDIDATES = LOADED_OVERALL_HISTOGRAM + 0.05 ; const LOADED_DONE = 1 ; const PROGRESS_STEP_P_VALUES = 0.9 ; export function useFailedTransactionsCorrelations ( ) { const fetchParams = useFetchParams ( ) ; const [ response , setResponseUnDebounced ] = useReducer ( getReducer < FailedTransactionsCorrelationsResponse & CorrelationsProgress > ( ) , getInitialResponse ( ) ) ; const setResponse = useMemo ( ( ) => debounce ( setResponseUnDebounced , DEBOUNCE_INTERVAL ) , [ ] ) ; const abortCtrl = useRef ( new AbortController ( ) ) ; const startFetch = useCallback ( async ( ) => { abortCtrl . current . abort ( ) ; abortCtrl . current = new AbortController ( ) ; setResponse ( { ... getInitialResponse ( ) , isRunning : true , error : undefined , failedTransactionsCorrelations : undefined , percentileThresholdValue : undefined , overallHistogram : undefined , errorHistogram : undefined , fieldStats : undefined , } ) ; setResponse . flush ( ) ; try { const responseUpdate = { ccsWarning : false , } ; const [ overallHistogramResponse , errorHistogramRespone ] = await Promise . all ( [ callApmApi ( { endpoint : 'POST /internal/apm/latency/overall_distribution' , signal : abortCtrl . current . signal , params : { body : { ... fetchParams , percentileThreshold : DEFAULT_PERCENTILE_THRESHOLD , } , } , } ) , callApmApi ( { endpoint : 'POST /internal/apm/latency/overall_distribution' , signal : abortCtrl . current . signal , params : { body : { ... fetchParams , percentileThreshold : DEFAULT_PERCENTILE_THRESHOLD , termFilters : [ { fieldName : EVENT_OUTCOME , fieldValue : EventOutcome . failure , } , ] , } , } , } ) , ] ) ; const { overallHistogram , percentileThresholdValue } = overallHistogramResponse ; const { overallHistogram : errorHistogram } = errorHistogramRespone ; responseUpdate . errorHistogram = errorHistogram ; responseUpdate . overallHistogram = overallHistogram ; responseUpdate . percentileThresholdValue = percentileThresholdValue ; if ( abortCtrl . current . signal . aborted ) { return ; } setResponse ( { ... responseUpdate , loaded : LOADED_OVERALL_HISTOGRAM , } ) ; setResponse . flush ( ) ; const { fieldCandidates : candidates } = await callApmApi ( { endpoint : 'GET /internal/apm/correlations/field_candidates' , signal : abortCtrl . current . signal , params : { query : fetchParams , } , } ) ; if ( abortCtrl . current . signal . aborted ) { return ; } const fieldCandidates = candidates . filter ( ( t ) => ! ( t === EVENT_OUTCOME ) ) ; setResponse ( { loaded : LOADED_FIELD_CANDIDATES , } ) ; setResponse . flush ( ) ; const failedTransactionsCorrelations = [ ] ; const fieldsToSample = new Set < string > ( ) ; const chunkSize = 10 ; let chunkLoadCounter = 0 ; const fieldCandidatesChunks = chunk ( fieldCandidates , chunkSize ) ; for ( const fieldCandidatesChunk of fieldCandidatesChunks ) { const pValues = await callApmApi ( { endpoint : 'POST /internal/apm/correlations/p_values' , signal : abortCtrl . current . signal , params : { body : { ... fetchParams , fieldCandidates : fieldCandidatesChunk } , } , } ) ; if ( pValues . failedTransactionsCorrelations . length > 0 ) { pValues . failedTransactionsCorrelations . forEach ( ( d ) => { fieldsToSample . add ( d . fieldName ) ; } ) ; failedTransactionsCorrelations . push ( ... pValues . failedTransactionsCorrelations ) ; responseUpdate . failedTransactionsCorrelations = getFailedTransactionsCorrelationsSortedByScore ( [ ... failedTransactionsCorrelations , ] ) ; } chunkLoadCounter ++ ; setResponse ( { ... responseUpdate , loaded : LOADED_FIELD_CANDIDATES + ( chunkLoadCounter / fieldCandidatesChunks . length ) * PROGRESS_STEP_P_VALUES , } ) ; if ( abortCtrl . current . signal . aborted ) { return ; } } setResponse . flush ( ) ; const { stats } = await callApmApi ( { endpoint : 'POST /internal/apm/correlations/field_stats' , signal : abortCtrl . current . signal , params : { body : { ... fetchParams , fieldsToSample : [ ... fieldsToSample ] , } , } , } ) ; responseUpdate . fieldStats = stats ; setResponse ( { ... responseUpdate , loaded : LOADED_DONE , isRunning : false } ) ; setResponse . flush ( ) ; } catch ( e ) { if ( ! abortCtrl . current . signal . aborted ) { const err = e as Error | IHttpFetchError < ResponseErrorBody > ; setResponse ( { error : 'response' in err ? err . body ?. message ?? err . response ?. statusText : err . message , isRunning : false , } ) ; setResponse . flush ( ) ; } } } , [ fetchParams , setResponse ] ) ; const cancelFetch = useCallback ( ( ) => { abortCtrl . current . abort ( ) ; setResponse ( { isRunning : false , } ) ; setResponse . flush ( ) ; } , [ setResponse ] ) ; useEffect ( ( ) => { startFetch ( ) ; return ( ) => { abortCtrl . current . abort ( ) ; } ; } , [ startFetch , cancelFetch ] ) ; const { error , loaded , isRunning , ... returnedResponse } = response ; const progress = useMemo ( ( ) => ( { error , loaded , isRunning , } ) , [ error , loaded , isRunning ] ) ; return { progress , response : returnedResponse , startFetch , cancelFetch , } ; }