From 39f7cff7c75914962c73ee1c64140dcd4d1152ec Mon Sep 17 00:00:00 2001 From: Andrej Ocenas Date: Fri, 24 Jan 2020 15:07:45 +0100 Subject: [PATCH] Loki: Refactor editor and syntax hooks (#21687) --- .../components/AnnotationsQueryEditor.tsx | 9 ++- .../loki/components/LokiQueryEditor.tsx | 10 --- .../loki/components/LokiQueryField.tsx | 20 +++-- .../loki/components/LokiQueryFieldForm.tsx | 2 - .../loki/components/useLokiLabels.test.ts | 6 +- .../loki/components/useLokiLabels.ts | 28 ++++--- .../loki/components/useLokiSyntax.ts | 60 -------------- ...test.ts => useLokiSyntaxAndLabels.test.ts} | 8 +- .../loki/components/useLokiSyntaxAndLabels.ts | 79 +++++++++++++++++++ .../datasource/loki/language_provider.ts | 3 +- .../prometheus/components/PromQueryField.tsx | 6 +- 11 files changed, 130 insertions(+), 101 deletions(-) delete mode 100644 public/app/plugins/datasource/loki/components/useLokiSyntax.ts rename public/app/plugins/datasource/loki/components/{useLokiSyntax.test.ts => useLokiSyntaxAndLabels.test.ts} (91%) create mode 100644 public/app/plugins/datasource/loki/components/useLokiSyntaxAndLabels.ts diff --git a/public/app/plugins/datasource/loki/components/AnnotationsQueryEditor.tsx b/public/app/plugins/datasource/loki/components/AnnotationsQueryEditor.tsx index 0bd81cd46b9..dba7c85c792 100644 --- a/public/app/plugins/datasource/loki/components/AnnotationsQueryEditor.tsx +++ b/public/app/plugins/datasource/loki/components/AnnotationsQueryEditor.tsx @@ -3,7 +3,7 @@ import React, { memo } from 'react'; // Types import { LokiQuery } from '../types'; -import { useLokiSyntax } from './useLokiSyntax'; +import { useLokiSyntaxAndLabels } from './useLokiSyntaxAndLabels'; import { LokiQueryFieldForm } from './LokiQueryFieldForm'; import LokiDatasource from '../datasource'; @@ -22,7 +22,7 @@ export const LokiAnnotationsQueryEditor = memo(function LokiAnnotationQueryEdito to: Date.now(), }; - const { isSyntaxReady, setActiveOption, refreshLabels, ...syntaxProps } = useLokiSyntax( + const { isSyntaxReady, setActiveOption, refreshLabels, syntax, logLabelOptions } = useLokiSyntaxAndLabels( datasource.languageProvider, absolute ); @@ -43,9 +43,10 @@ export const LokiAnnotationsQueryEditor = memo(function LokiAnnotationQueryEdito data={null} onLoadOptions={setActiveOption} onLabelsRefresh={refreshLabels} - syntaxLoaded={isSyntaxReady} absoluteRange={absolute} - {...syntaxProps} + syntax={syntax} + syntaxLoaded={isSyntaxReady} + logLabelOptions={logLabelOptions} /> ); diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx index ab95e0cba1a..414e57ff853 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx @@ -6,7 +6,6 @@ import { AbsoluteTimeRange, QueryEditorProps } from '@grafana/data'; import { LokiDatasource } from '../datasource'; import { LokiQuery } from '../types'; import { LokiQueryField } from './LokiQueryField'; -import { useLokiSyntax } from './useLokiSyntax'; type Props = QueryEditorProps; @@ -27,11 +26,6 @@ export const LokiQueryEditor = memo(function LokiQueryEditor(props: Props) { }; } - const { isSyntaxReady, setActiveOption, refreshLabels, ...syntaxProps } = useLokiSyntax( - datasource.languageProvider, - absolute - ); - return (
); diff --git a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx index 332225aa19e..45f29fe8c58 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx @@ -1,18 +1,23 @@ import React, { FunctionComponent } from 'react'; import { LokiQueryFieldForm, LokiQueryFieldFormProps } from './LokiQueryFieldForm'; -import { useLokiSyntax } from './useLokiSyntax'; +import { useLokiSyntaxAndLabels } from './useLokiSyntaxAndLabels'; import LokiLanguageProvider from '../language_provider'; -export const LokiQueryField: FunctionComponent = ({ datasource, ...otherProps }) => { - const { isSyntaxReady, setActiveOption, refreshLabels, ...syntaxProps } = useLokiSyntax( +type LokiQueryFieldProps = Omit< + LokiQueryFieldFormProps, + 'syntax' | 'syntaxLoaded' | 'onLoadOptions' | 'onLabelsRefresh' | 'logLabelOptions' +>; + +export const LokiQueryField: FunctionComponent = props => { + const { datasource, absoluteRange, ...otherProps } = props; + const { isSyntaxReady, setActiveOption, refreshLabels, syntax, logLabelOptions } = useLokiSyntaxAndLabels( datasource.languageProvider as LokiLanguageProvider, - otherProps.absoluteRange + absoluteRange ); return ( = ({ dat */ onLoadOptions={setActiveOption} onLabelsRefresh={refreshLabels} - {...syntaxProps} + absoluteRange={absoluteRange} + syntax={syntax} + syntaxLoaded={isSyntaxReady} + logLabelOptions={logLabelOptions} {...otherProps} /> ); diff --git a/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx b/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx index 7dd39f0d008..28af42836a0 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx @@ -73,8 +73,6 @@ export interface LokiQueryFieldFormProps extends ExploreQueryFieldProps { plugins: Plugin[]; - modifiedSearch: string; - modifiedQuery: string; constructor(props: LokiQueryFieldFormProps, context: React.Context) { super(props, context); diff --git a/public/app/plugins/datasource/loki/components/useLokiLabels.test.ts b/public/app/plugins/datasource/loki/components/useLokiLabels.test.ts index 313e1d89f85..e38b0d522d9 100644 --- a/public/app/plugins/datasource/loki/components/useLokiLabels.test.ts +++ b/public/app/plugins/datasource/loki/components/useLokiLabels.test.ts @@ -14,14 +14,16 @@ describe('useLokiLabels hook', () => { to: 1560153109000, }; + languageProvider.logLabelOptions = ['initial']; + languageProvider.refreshLogLabels = () => { languageProvider.logLabelOptions = logLabelOptionsMock; return Promise.resolve(); }; - const { result, waitForNextUpdate } = renderHook(() => useLokiLabels(languageProvider, true, [], rangeMock)); + const { result, waitForNextUpdate } = renderHook(() => useLokiLabels(languageProvider, true, rangeMock)); + expect(result.current.logLabelOptions).toEqual(['initial']); act(() => result.current.refreshLabels()); - expect(result.current.logLabelOptions).toEqual([]); await waitForNextUpdate(); expect(result.current.logLabelOptions).toEqual(logLabelOptionsMock); }); diff --git a/public/app/plugins/datasource/loki/components/useLokiLabels.ts b/public/app/plugins/datasource/loki/components/useLokiLabels.ts index e08382cdbbf..74ff17a373d 100644 --- a/public/app/plugins/datasource/loki/components/useLokiLabels.ts +++ b/public/app/plugins/datasource/loki/components/useLokiLabels.ts @@ -9,14 +9,13 @@ import { useRefMounted } from 'app/core/hooks/useRefMounted'; * * @param languageProvider * @param languageProviderInitialised - * @param activeOption rc-cascader provided option used to fetch option's values that hasn't been loaded yet + * @param absoluteRange * * @description Fetches missing labels and enables labels refresh */ export const useLokiLabels = ( languageProvider: LokiLanguageProvider, languageProviderInitialised: boolean, - activeOption: CascaderOption[], absoluteRange: AbsoluteTimeRange ) => { const mounted = useRefMounted(); @@ -24,7 +23,12 @@ export const useLokiLabels = ( // State const [logLabelOptions, setLogLabelOptions] = useState([]); const [shouldTryRefreshLabels, setRefreshLabels] = useState(false); - const [shouldForceRefreshLabels, setForceRefreshLabels] = useState(false); + /** + * Holds information about currently selected option from rc-cascader to perform effect + * that loads option values not fetched yet. Based on that useLokiLabels hook decides whether or not + * the option requires additional data fetching + */ + const [activeOption, setActiveOption] = useState([]); // Async const fetchOptionValues = async (option: string) => { @@ -35,11 +39,10 @@ export const useLokiLabels = ( }; const tryLabelsRefresh = async () => { - await languageProvider.refreshLogLabels(absoluteRange, shouldForceRefreshLabels); + await languageProvider.refreshLogLabels(absoluteRange); if (mounted.current) { setRefreshLabels(false); - setForceRefreshLabels(false); setLogLabelOptions(languageProvider.logLabelOptions); } }; @@ -68,18 +71,25 @@ export const useLokiLabels = ( } }, [activeOption]); - // This effect is performed on shouldTryRefreshLabels or shouldForceRefreshLabels state change only. + // This effect is performed on shouldTryRefreshLabels state change only. // Since shouldTryRefreshLabels is reset AFTER the labels are refreshed we are secured in case of trying to refresh // when previous refresh hasn't finished yet useEffect(() => { - if (shouldTryRefreshLabels || shouldForceRefreshLabels) { + if (shouldTryRefreshLabels) { tryLabelsRefresh(); } - }, [shouldTryRefreshLabels, shouldForceRefreshLabels]); + }, [shouldTryRefreshLabels]); + + // Initialize labels from the provider after it gets initialized (it's initialisation happens outside of this hook) + useEffect(() => { + if (languageProviderInitialised) { + setLogLabelOptions(languageProvider.logLabelOptions); + } + }, [languageProviderInitialised]); return { logLabelOptions, - setLogLabelOptions, refreshLabels: () => setRefreshLabels(true), + setActiveOption, }; }; diff --git a/public/app/plugins/datasource/loki/components/useLokiSyntax.ts b/public/app/plugins/datasource/loki/components/useLokiSyntax.ts deleted file mode 100644 index 2285de5cf2e..00000000000 --- a/public/app/plugins/datasource/loki/components/useLokiSyntax.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { useState, useEffect } from 'react'; -import Prism from 'prismjs'; -import { AbsoluteTimeRange } from '@grafana/data'; -import { CascaderOption } from '@grafana/ui'; -import LokiLanguageProvider from 'app/plugins/datasource/loki/language_provider'; -import { useLokiLabels } from 'app/plugins/datasource/loki/components/useLokiLabels'; -import { useRefMounted } from 'app/core/hooks/useRefMounted'; - -const PRISM_SYNTAX = 'promql'; - -/** - * - * @param languageProvider - * @description Initializes given language provider, exposes Loki syntax and enables loading label option values - */ -export const useLokiSyntax = (languageProvider: LokiLanguageProvider, absoluteRange: AbsoluteTimeRange) => { - const mounted = useRefMounted(); - // State - const [languageProviderInitialized, setLanguageProviderInitilized] = useState(false); - const [syntax, setSyntax] = useState(null); - - /** - * Holds information about currently selected option from rc-cascader to perform effect - * that loads option values not fetched yet. Based on that useLokiLabels hook decides whether or not - * the option requires additional data fetching - */ - const [activeOption, setActiveOption] = useState(); - - const { logLabelOptions, setLogLabelOptions, refreshLabels } = useLokiLabels( - languageProvider, - languageProviderInitialized, - activeOption, - absoluteRange - ); - - // Async - const initializeLanguageProvider = async () => { - languageProvider.initialRange = absoluteRange; - await languageProvider.start(); - Prism.languages[PRISM_SYNTAX] = languageProvider.getSyntax(); - if (mounted.current) { - setLogLabelOptions(languageProvider.logLabelOptions); - setSyntax(languageProvider.getSyntax()); - setLanguageProviderInitilized(true); - } - }; - - // Effects - useEffect(() => { - initializeLanguageProvider(); - }, []); - - return { - isSyntaxReady: languageProviderInitialized, - syntax, - logLabelOptions, - setActiveOption, - refreshLabels, - }; -}; diff --git a/public/app/plugins/datasource/loki/components/useLokiSyntax.test.ts b/public/app/plugins/datasource/loki/components/useLokiSyntaxAndLabels.test.ts similarity index 91% rename from public/app/plugins/datasource/loki/components/useLokiSyntax.test.ts rename to public/app/plugins/datasource/loki/components/useLokiSyntaxAndLabels.test.ts index 883a55ca5cb..22b4078b58d 100644 --- a/public/app/plugins/datasource/loki/components/useLokiSyntax.test.ts +++ b/public/app/plugins/datasource/loki/components/useLokiSyntaxAndLabels.test.ts @@ -4,7 +4,7 @@ import { CascaderOption } from '@grafana/ui'; import LanguageProvider from 'app/plugins/datasource/loki/language_provider'; -import { useLokiSyntax } from './useLokiSyntax'; +import { useLokiSyntaxAndLabels } from './useLokiSyntaxAndLabels'; import { makeMockLokiDatasource } from '../mocks'; describe('useLokiSyntax hook', () => { @@ -35,7 +35,7 @@ describe('useLokiSyntax hook', () => { }; it('should provide Loki syntax when used', async () => { - const { result, waitForNextUpdate } = renderHook(() => useLokiSyntax(languageProvider, rangeMock)); + const { result, waitForNextUpdate } = renderHook(() => useLokiSyntaxAndLabels(languageProvider, rangeMock)); expect(result.current.syntax).toEqual(null); await waitForNextUpdate(); @@ -44,7 +44,7 @@ describe('useLokiSyntax hook', () => { }); it('should fetch labels on first call', async () => { - const { result, waitForNextUpdate } = renderHook(() => useLokiSyntax(languageProvider, rangeMock)); + const { result, waitForNextUpdate } = renderHook(() => useLokiSyntaxAndLabels(languageProvider, rangeMock)); expect(result.current.isSyntaxReady).toBeFalsy(); expect(result.current.logLabelOptions).toEqual([]); @@ -55,7 +55,7 @@ describe('useLokiSyntax hook', () => { }); it('should try to fetch missing options when active option changes', async () => { - const { result, waitForNextUpdate } = renderHook(() => useLokiSyntax(languageProvider, rangeMock)); + const { result, waitForNextUpdate } = renderHook(() => useLokiSyntaxAndLabels(languageProvider, rangeMock)); await waitForNextUpdate(); expect(result.current.logLabelOptions).toEqual(logLabelOptionsMock2); diff --git a/public/app/plugins/datasource/loki/components/useLokiSyntaxAndLabels.ts b/public/app/plugins/datasource/loki/components/useLokiSyntaxAndLabels.ts new file mode 100644 index 00000000000..b0faca472d6 --- /dev/null +++ b/public/app/plugins/datasource/loki/components/useLokiSyntaxAndLabels.ts @@ -0,0 +1,79 @@ +import { useState, useEffect } from 'react'; +import Prism, { Grammar } from 'prismjs'; +import { AbsoluteTimeRange } from '@grafana/data'; +import LokiLanguageProvider from 'app/plugins/datasource/loki/language_provider'; +import { useLokiLabels } from 'app/plugins/datasource/loki/components/useLokiLabels'; +import { useRefMounted } from 'app/core/hooks/useRefMounted'; + +const PRISM_SYNTAX = 'promql'; + +/** + * Initialise the language provider. Returns a languageProviderInitialized boolean cause there does not seem other way + * to know if the provider is already initialised or not. By the initialisation it modifies the provided + * languageProvider directly. + */ +const useInitLanguageProvider = (languageProvider: LokiLanguageProvider, absoluteRange: AbsoluteTimeRange) => { + const mounted = useRefMounted(); + + const [languageProviderInitialized, setLanguageProviderInitialized] = useState(false); + + // Async + const initializeLanguageProvider = async () => { + languageProvider.initialRange = absoluteRange; + await languageProvider.start(); + if (mounted.current) { + setLanguageProviderInitialized(true); + } + }; + + useEffect(() => { + initializeLanguageProvider(); + }, []); + + return languageProviderInitialized; +}; + +/** + * Returns syntax from languageProvider and initialises global Prism syntax. Waits until languageProvider itself is + * initialised (outside of this hook). + */ +const useLokiSyntax = (languageProvider: LokiLanguageProvider, languageProviderInitialized: boolean) => { + // State + const [syntax, setSyntax] = useState(null); + + // Effects + useEffect(() => { + if (languageProviderInitialized) { + const syntax = languageProvider.getSyntax(); + Prism.languages[PRISM_SYNTAX] = syntax; + setSyntax(syntax); + } + }, [languageProviderInitialized, languageProvider]); + + return { + isSyntaxReady: !!syntax, + syntax, + }; +}; + +/** + * Initializes given language provider, exposes Loki syntax and enables loading label option values + */ +export const useLokiSyntaxAndLabels = (languageProvider: LokiLanguageProvider, absoluteRange: AbsoluteTimeRange) => { + const languageProviderInitialized = useInitLanguageProvider(languageProvider, absoluteRange); + + const { logLabelOptions, refreshLabels, setActiveOption } = useLokiLabels( + languageProvider, + languageProviderInitialized, + absoluteRange + ); + const { isSyntaxReady, syntax } = useLokiSyntax(languageProvider, languageProviderInitialized); + + return { + isSyntaxReady, + syntax, + logLabelOptions, + setActiveOption, + refreshLabels, + }; +}; diff --git a/public/app/plugins/datasource/loki/language_provider.ts b/public/app/plugins/datasource/loki/language_provider.ts index 38b730389cc..3f8af4ac124 100644 --- a/public/app/plugins/datasource/loki/language_provider.ts +++ b/public/app/plugins/datasource/loki/language_provider.ts @@ -13,6 +13,7 @@ import { RATE_RANGES } from '../prometheus/promql'; import LokiDatasource from './datasource'; import { CompletionItem, TypeaheadInput, TypeaheadOutput } from '@grafana/ui'; +import { Grammar } from 'prismjs'; const DEFAULT_KEYS = ['job', 'namespace']; const EMPTY_SELECTOR = '{}'; @@ -70,7 +71,7 @@ export default class LokiLanguageProvider extends LanguageProvider { // Strip syntax chars cleanText = (s: string) => s.replace(/[{}[\]="(),!~+\-*/^%]/g, '').trim(); - getSyntax() { + getSyntax(): Grammar { return syntax; } diff --git a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx index 4287a032bf6..f373061feea 100644 --- a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx @@ -188,9 +188,9 @@ class PromQueryField extends React.PureComponent) => task.then(this.onUpdateLanguage).catch(() => {})); }) .then(() => this.onUpdateLanguage()) - .catch(({ isCanceled }) => { - if (isCanceled) { - console.warn('PromQueryField has unmounted, language provider intialization was canceled'); + .catch(err => { + if (!err.isCanceled) { + throw err; } }); };