import { i18n } from '@kbn/i18n' ; import React , { useEffect , useMemo , useState } from 'react' ; import { IHttpFetchError , ResponseErrorBody } from 'src/core/public' ; import { useKibana } from '../../../../../src/plugins/kibana_react/public' ; import { useTimeRangeId } from '../context/time_range_id/use_time_range_id' ; import { AutoAbortedAPMClient , callApmApi , } from '../services/rest/createCallApmApi' ; import { useInspectorContext } from '../../../observability/public' ; export enum FETCH_STATUS { LOADING = 'loading' , SUCCESS = 'success' , FAILURE = 'failure' , NOT_INITIATED = 'not_initiated' } export interface FetcherResult < Data > { data ? : Data ; status : FETCH_STATUS ; error ? : IHttpFetchError < ResponseErrorBody > ; } function getDetailsFromErrorResponse ( error ) { const message = error . body ?. message ?? error . response ?. statusText ; return ( < > { message } ( { error . response ?. status } ) < h5 > { i18n . translate ( 'xpack.apm.fetcher.error.url' , { defaultMessage : `URL` , } ) } < /h5> { error . response ?. url } < />); } const createAutoAbortedAPMClient = ( signal ) : AutoAbortedAPMClient => { return ( ( options ) => { return callApmApi ( { ... options , signal } ) ; } ) as AutoAbortedAPMClient ; } ; type InferResponseType < TReturn > = Exclude < TReturn , undefined > extends Promise < infer TResponseType > ? TResponseType : unknown ; export function useFetcher < TReturn > ( fn , fnDeps , options = { } ) { const { notifications } = useKibana ( ) ; const { preservePreviousData = true , showToastOnError = true } = options ; const [ result , setResult ] = useState < FetcherResult < InferResponseType < TReturn > > > ( { data : undefined , status : FETCH_STATUS . NOT_INITIATED , } ) ; const [ counter , setCounter ] = useState ( 0 ) ; const { timeRangeId } = useTimeRangeId ( ) ; const { addInspectorRequest } = useInspectorContext ( ) ; useEffect ( ( ) => { let controller = new AbortController ( ) ; async function doFetch ( ) { controller . abort ( ) ; controller = new AbortController ( ) ; const signal = controller . signal ; const promise = fn ( createAutoAbortedAPMClient ( signal ) ) ; if ( ! promise ) { return ; } setResult ( ( prevResult ) => ( { data : preservePreviousData ? prevResult . data : undefined , status : FETCH_STATUS . LOADING , error : undefined , } ) ) ; try { const data = await promise ; if ( ! signal . aborted ) { setResult ( { data , status : FETCH_STATUS . SUCCESS , error : undefined , } as FetcherResult < InferResponseType < TReturn > > ) ; } } catch ( e ) { const err = e as Error | IHttpFetchError < ResponseErrorBody > ; if ( ! signal . aborted ) { const errorDetails = 'response' in err ? getDetailsFromErrorResponse ( err ) : err . message ; if ( showToastOnError ) { notifications . toasts . danger ( { title : i18n . translate ( 'xpack.apm.fetcher.error.title' , { defaultMessage : `Error while fetching resource` , } ) , body : ( < div > < h5 > { i18n . translate ( 'xpack.apm.fetcher.error.status' , { defaultMessage : `Error` , } ) } < /h5> { errorDetails } < /div>), } ) ; } setResult ( { data : undefined , status : FETCH_STATUS . FAILURE , error : e , } ) ; } } } doFetch ( ) ; return ( ) => { controller . abort ( ) ; } ; } , [ counter , preservePreviousData , timeRangeId , showToastOnError , ... fnDeps , ] ) ; useEffect ( ( ) => { if ( result . error ) { addInspectorRequest ( { ... result , data : result . error . body ?. attributes } ) ; } else { addInspectorRequest ( result ) ; } } , [ addInspectorRequest , result ] ) ; return useMemo ( ( ) => { return { ... result , refetch : ( ) => { setCounter ( ( count ) => count + 1 ) ; } , } ; } , [ result ] ) ; }