1import React, { createContext, Dispatch, PropsWithChildren, useContext, useEffect, useMemo, useState } from 'react'; 2import { AnyAction } from '@reduxjs/toolkit'; 3import { QueryEditorProps } from '@grafana/data'; 4import { GraphiteDatasource } from '../datasource'; 5import { GraphiteOptions, GraphiteQuery } from '../types'; 6import { createStore, GraphiteQueryEditorState } from './store'; 7import { getTemplateSrv } from 'app/features/templating/template_srv'; 8import { actions } from './actions'; 9import { usePrevious } from 'react-use'; 10 11const DispatchContext = createContext<Dispatch<AnyAction>>({} as Dispatch<AnyAction>); 12const GraphiteStateContext = createContext<GraphiteQueryEditorState>({} as GraphiteQueryEditorState); 13 14export const useDispatch = () => { 15 return useContext(DispatchContext); 16}; 17 18export const useGraphiteState = () => { 19 return useContext(GraphiteStateContext); 20}; 21 22export type GraphiteQueryEditorProps = QueryEditorProps<GraphiteDatasource, GraphiteQuery, GraphiteOptions>; 23 24export const GraphiteQueryEditorContext = ({ 25 datasource, 26 onRunQuery, 27 onChange, 28 query, 29 queries, 30 range, 31 children, 32}: PropsWithChildren<GraphiteQueryEditorProps>) => { 33 const [state, setState] = useState<GraphiteQueryEditorState>(); 34 const [needsRefresh, setNeedsRefresh] = useState<boolean>(false); 35 36 const dispatch = useMemo(() => { 37 return createStore((state) => { 38 setState(state); 39 }); 40 }, []); 41 42 // synchronise changes provided in props with editor's state 43 const previousRange = usePrevious(range); 44 useEffect(() => { 45 if (previousRange?.raw !== range?.raw) { 46 dispatch(actions.timeRangeChanged(range)); 47 } 48 }, [dispatch, range, previousRange]); 49 50 useEffect( 51 () => { 52 if (state) { 53 dispatch(actions.queriesChanged(queries)); 54 } 55 }, 56 // adding state to dependencies causes infinite loops 57 // eslint-disable-next-line react-hooks/exhaustive-deps 58 [dispatch, queries] 59 ); 60 61 useEffect( 62 () => { 63 if (state && state.target?.target !== query.target) { 64 dispatch(actions.queryChanged(query)); 65 } 66 }, 67 // adding state to dependencies causes infinite loops 68 // eslint-disable-next-line react-hooks/exhaustive-deps 69 [dispatch, query] 70 ); 71 72 useEffect( 73 () => { 74 if (needsRefresh && state) { 75 setNeedsRefresh(false); 76 onChange({ ...query, target: state.target.target }); 77 onRunQuery(); 78 } 79 }, 80 // adding state to dependencies causes infinite loops 81 // eslint-disable-next-line react-hooks/exhaustive-deps 82 [needsRefresh, onChange, onRunQuery, query] 83 ); 84 85 if (!state) { 86 dispatch( 87 actions.init({ 88 target: query, 89 datasource: datasource, 90 range: range, 91 templateSrv: getTemplateSrv(), 92 // list of queries is passed only when the editor is in Dashboards. This is to allow interpolation 93 // of sub-queries which are stored in "targetFull" property used by alerting in the backend. 94 queries: queries || [], 95 refresh: () => { 96 // do not run onChange/onRunQuery straight away to ensure the internal state gets updated first 97 // to avoid race conditions (onChange could update props before the reducer action finishes) 98 setNeedsRefresh(true); 99 }, 100 }) 101 ); 102 return null; 103 } else { 104 return ( 105 <GraphiteStateContext.Provider value={state}> 106 <DispatchContext.Provider value={dispatch}>{children}</DispatchContext.Provider> 107 </GraphiteStateContext.Provider> 108 ); 109 } 110}; 111