context.tsx 3.5 KB

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