mirror of
https://github.com/grafana/grafana.git
synced 2025-02-13 00:55:47 -06:00
Loki: Refactor editor and syntax hooks (#21687)
This commit is contained in:
parent
b28eac2626
commit
39f7cff7c7
@ -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}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -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<LokiDatasource, LokiQuery>;
|
||||
|
||||
@ -27,11 +26,6 @@ export const LokiQueryEditor = memo(function LokiQueryEditor(props: Props) {
|
||||
};
|
||||
}
|
||||
|
||||
const { isSyntaxReady, setActiveOption, refreshLabels, ...syntaxProps } = useLokiSyntax(
|
||||
datasource.languageProvider,
|
||||
absolute
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LokiQueryField
|
||||
@ -41,11 +35,7 @@ export const LokiQueryEditor = memo(function LokiQueryEditor(props: Props) {
|
||||
onRunQuery={onRunQuery}
|
||||
history={[]}
|
||||
data={data}
|
||||
onLoadOptions={setActiveOption}
|
||||
onLabelsRefresh={refreshLabels}
|
||||
syntaxLoaded={isSyntaxReady}
|
||||
absoluteRange={absolute}
|
||||
{...syntaxProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -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<LokiQueryFieldFormProps> = ({ datasource, ...otherProps }) => {
|
||||
const { isSyntaxReady, setActiveOption, refreshLabels, ...syntaxProps } = useLokiSyntax(
|
||||
type LokiQueryFieldProps = Omit<
|
||||
LokiQueryFieldFormProps,
|
||||
'syntax' | 'syntaxLoaded' | 'onLoadOptions' | 'onLabelsRefresh' | 'logLabelOptions'
|
||||
>;
|
||||
|
||||
export const LokiQueryField: FunctionComponent<LokiQueryFieldProps> = props => {
|
||||
const { datasource, absoluteRange, ...otherProps } = props;
|
||||
const { isSyntaxReady, setActiveOption, refreshLabels, syntax, logLabelOptions } = useLokiSyntaxAndLabels(
|
||||
datasource.languageProvider as LokiLanguageProvider,
|
||||
otherProps.absoluteRange
|
||||
absoluteRange
|
||||
);
|
||||
|
||||
return (
|
||||
<LokiQueryFieldForm
|
||||
datasource={datasource}
|
||||
syntaxLoaded={isSyntaxReady}
|
||||
/**
|
||||
* setActiveOption name is intentional. Because of the way rc-cascader requests additional data
|
||||
* https://github.com/react-component/cascader/blob/master/src/Cascader.jsx#L165
|
||||
@ -21,7 +26,10 @@ export const LokiQueryField: FunctionComponent<LokiQueryFieldFormProps> = ({ dat
|
||||
*/
|
||||
onLoadOptions={setActiveOption}
|
||||
onLabelsRefresh={refreshLabels}
|
||||
{...syntaxProps}
|
||||
absoluteRange={absoluteRange}
|
||||
syntax={syntax}
|
||||
syntaxLoaded={isSyntaxReady}
|
||||
logLabelOptions={logLabelOptions}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
|
@ -73,8 +73,6 @@ export interface LokiQueryFieldFormProps extends ExploreQueryFieldProps<LokiData
|
||||
|
||||
export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormProps> {
|
||||
plugins: Plugin[];
|
||||
modifiedSearch: string;
|
||||
modifiedQuery: string;
|
||||
|
||||
constructor(props: LokiQueryFieldFormProps, context: React.Context<any>) {
|
||||
super(props, context);
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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<CascaderOption[]>([]);
|
||||
|
||||
// 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,
|
||||
};
|
||||
};
|
||||
|
@ -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<CascaderOption[]>();
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
@ -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);
|
||||
|
@ -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<Grammar>(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,
|
||||
};
|
||||
};
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -188,9 +188,9 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
||||
remaining.map((task: Promise<any>) => 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;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user