mirror of
https://github.com/grafana/grafana.git
synced 2025-08-02 03:02:18 +08:00
Prometheus: Refresh query field metrics on data source change (#24116)
... in `componentDidUpdate`, not just `componentDidMount`. Also unify query field behavior of Explore with Dashboard - when the data source changes, it doesn't unmount but instead refreshes its metrics. Fixes #23162
This commit is contained in:

committed by
GitHub

parent
e9243215a6
commit
827f99f0cb
@ -20,8 +20,8 @@ export default class QueryRows extends PureComponent<QueryRowsProps> {
|
|||||||
const { className = '', exploreEvents, exploreId, queryKeys } = this.props;
|
const { className = '', exploreEvents, exploreId, queryKeys } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
{queryKeys.map((key, index) => {
|
{queryKeys.map((_, index) => {
|
||||||
return <QueryRow key={key} exploreEvents={exploreEvents} exploreId={exploreId} index={index} />;
|
return <QueryRow key={index} exploreEvents={exploreEvents} exploreId={exploreId} index={index} />;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -302,7 +302,6 @@ export const itemReducer = (state: ExploreItemState = makeExploreItemState(), ac
|
|||||||
latency: 0,
|
latency: 0,
|
||||||
queryResponse: createEmptyQueryResponse(),
|
queryResponse: createEmptyQueryResponse(),
|
||||||
loading: false,
|
loading: false,
|
||||||
queryKeys: [],
|
|
||||||
supportedModes,
|
supportedModes,
|
||||||
mode: mode ?? newMode,
|
mode: mode ?? newMode,
|
||||||
originPanelId: state.urlState && state.urlState.originPanelId,
|
originPanelId: state.urlState && state.urlState.originPanelId,
|
||||||
|
@ -1,4 +1,67 @@
|
|||||||
import { groupMetricsByPrefix, RECORDING_RULES_GROUP } from './PromQueryField';
|
import { mount } from 'enzyme';
|
||||||
|
// @ts-ignore
|
||||||
|
import RCCascader from 'rc-cascader';
|
||||||
|
import React from 'react';
|
||||||
|
import PromQlLanguageProvider, { DEFAULT_LOOKUP_METRICS_THRESHOLD } from '../language_provider';
|
||||||
|
import PromQueryField, { groupMetricsByPrefix, RECORDING_RULES_GROUP } from './PromQueryField';
|
||||||
|
|
||||||
|
describe('PromQueryField', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
window.getSelection = () => {};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('refreshes metrics when the data source changes', async () => {
|
||||||
|
const metrics = ['foo', 'bar'];
|
||||||
|
const languageProvider = ({
|
||||||
|
histogramMetrics: [] as any,
|
||||||
|
metrics,
|
||||||
|
metricsMetadata: {},
|
||||||
|
lookupsDisabled: false,
|
||||||
|
lookupMetricsThreshold: DEFAULT_LOOKUP_METRICS_THRESHOLD,
|
||||||
|
start: () => {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
},
|
||||||
|
} as unknown) as PromQlLanguageProvider;
|
||||||
|
|
||||||
|
const queryField = mount(
|
||||||
|
<PromQueryField
|
||||||
|
// @ts-ignore
|
||||||
|
datasource={{
|
||||||
|
languageProvider,
|
||||||
|
}}
|
||||||
|
query={{ expr: '', refId: '' }}
|
||||||
|
onRunQuery={() => {}}
|
||||||
|
onChange={() => {}}
|
||||||
|
history={[]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
const cascader = queryField.find<RCCascader>(RCCascader);
|
||||||
|
cascader.simulate('click');
|
||||||
|
const cascaderNode: HTMLElement = cascader.instance().getPopupDOMNode();
|
||||||
|
|
||||||
|
for (const item of Array.from(cascaderNode.getElementsByTagName('li'))) {
|
||||||
|
expect(metrics.includes(item.innerHTML)).toBe(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const changedMetrics = ['baz', 'moo'];
|
||||||
|
queryField.setProps({
|
||||||
|
datasource: {
|
||||||
|
languageProvider: {
|
||||||
|
...languageProvider,
|
||||||
|
metrics: changedMetrics,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
for (const item of Array.from(cascaderNode.getElementsByTagName('li'))) {
|
||||||
|
expect(changedMetrics.includes(item.innerHTML)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('groupMetricsByPrefix()', () => {
|
describe('groupMetricsByPrefix()', () => {
|
||||||
it('returns an empty group for no metrics', () => {
|
it('returns an empty group for no metrics', () => {
|
||||||
|
@ -20,7 +20,6 @@ import { CancelablePromise, makePromiseCancelable } from 'app/core/utils/Cancela
|
|||||||
import { ExploreQueryFieldProps, QueryHint, isDataFrame, toLegacyResponseData, HistoryItem } from '@grafana/data';
|
import { ExploreQueryFieldProps, QueryHint, isDataFrame, toLegacyResponseData, HistoryItem } from '@grafana/data';
|
||||||
import { DOMUtil, SuggestionsState } from '@grafana/ui';
|
import { DOMUtil, SuggestionsState } from '@grafana/ui';
|
||||||
import { PrometheusDatasource } from '../datasource';
|
import { PrometheusDatasource } from '../datasource';
|
||||||
import PromQlLanguageProvider from '../language_provider';
|
|
||||||
|
|
||||||
const HISTOGRAM_GROUP = '__histograms__';
|
const HISTOGRAM_GROUP = '__histograms__';
|
||||||
const PRISM_SYNTAX = 'promql';
|
const PRISM_SYNTAX = 'promql';
|
||||||
@ -121,16 +120,11 @@ interface PromQueryFieldState {
|
|||||||
|
|
||||||
class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryFieldState> {
|
class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryFieldState> {
|
||||||
plugins: Plugin[];
|
plugins: Plugin[];
|
||||||
languageProvider: PromQlLanguageProvider;
|
|
||||||
languageProviderInitializationPromise: CancelablePromise<any>;
|
languageProviderInitializationPromise: CancelablePromise<any>;
|
||||||
|
|
||||||
constructor(props: PromQueryFieldProps, context: React.Context<any>) {
|
constructor(props: PromQueryFieldProps, context: React.Context<any>) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
if (props.datasource.languageProvider) {
|
|
||||||
this.languageProvider = props.datasource.languageProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.plugins = [
|
this.plugins = [
|
||||||
BracesPlugin(),
|
BracesPlugin(),
|
||||||
SlatePrism({
|
SlatePrism({
|
||||||
@ -147,9 +141,8 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.languageProvider) {
|
if (this.props.datasource.languageProvider) {
|
||||||
Prism.languages[PRISM_SYNTAX] = this.languageProvider.syntax;
|
this.refreshMetrics();
|
||||||
this.refreshMetrics(makePromiseCancelable(this.languageProvider.start()));
|
|
||||||
}
|
}
|
||||||
this.refreshHint();
|
this.refreshHint();
|
||||||
}
|
}
|
||||||
@ -161,7 +154,14 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: PromQueryFieldProps) {
|
componentDidUpdate(prevProps: PromQueryFieldProps) {
|
||||||
const { data } = this.props;
|
const {
|
||||||
|
data,
|
||||||
|
datasource: { languageProvider },
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (languageProvider !== prevProps.datasource.languageProvider) {
|
||||||
|
this.refreshMetrics();
|
||||||
|
}
|
||||||
|
|
||||||
if (data && prevProps.data && prevProps.data.series !== data.series) {
|
if (data && prevProps.data && prevProps.data.series !== data.series) {
|
||||||
this.refreshHint();
|
this.refreshHint();
|
||||||
@ -182,8 +182,13 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
this.setState({ hint });
|
this.setState({ hint });
|
||||||
};
|
};
|
||||||
|
|
||||||
refreshMetrics = (cancelablePromise: CancelablePromise<any>) => {
|
refreshMetrics = () => {
|
||||||
this.languageProviderInitializationPromise = cancelablePromise;
|
const {
|
||||||
|
datasource: { languageProvider },
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
Prism.languages[PRISM_SYNTAX] = languageProvider.syntax;
|
||||||
|
this.languageProviderInitializationPromise = makePromiseCancelable(languageProvider.start());
|
||||||
this.languageProviderInitializationPromise.promise
|
this.languageProviderInitializationPromise.promise
|
||||||
.then(remaining => {
|
.then(remaining => {
|
||||||
remaining.map((task: Promise<any>) => task.then(this.onUpdateLanguage).catch(() => {}));
|
remaining.map((task: Promise<any>) => task.then(this.onUpdateLanguage).catch(() => {}));
|
||||||
@ -246,7 +251,8 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
metricsMetadata,
|
metricsMetadata,
|
||||||
lookupsDisabled,
|
lookupsDisabled,
|
||||||
lookupMetricsThreshold,
|
lookupMetricsThreshold,
|
||||||
} = this.languageProvider;
|
} = this.props.datasource.languageProvider;
|
||||||
|
|
||||||
if (!metrics) {
|
if (!metrics) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -275,14 +281,18 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
};
|
};
|
||||||
|
|
||||||
onTypeahead = async (typeahead: TypeaheadInput): Promise<TypeaheadOutput> => {
|
onTypeahead = async (typeahead: TypeaheadInput): Promise<TypeaheadOutput> => {
|
||||||
if (!this.languageProvider) {
|
const {
|
||||||
|
datasource: { languageProvider },
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (!languageProvider) {
|
||||||
return { suggestions: [] };
|
return { suggestions: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const { history } = this.props;
|
const { history } = this.props;
|
||||||
const { prefix, text, value, wrapperClasses, labelKey } = typeahead;
|
const { prefix, text, value, wrapperClasses, labelKey } = typeahead;
|
||||||
|
|
||||||
const result = await this.languageProvider.provideCompletionItems(
|
const result = await languageProvider.provideCompletionItems(
|
||||||
{ text, value, prefix, wrapperClasses, labelKey },
|
{ text, value, prefix, wrapperClasses, labelKey },
|
||||||
{ history }
|
{ history }
|
||||||
);
|
);
|
||||||
@ -293,9 +303,13 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { query, ExtraFieldElement } = this.props;
|
const {
|
||||||
|
datasource: { languageProvider },
|
||||||
|
query,
|
||||||
|
ExtraFieldElement,
|
||||||
|
} = this.props;
|
||||||
const { metricsOptions, syntaxLoaded, hint } = this.state;
|
const { metricsOptions, syntaxLoaded, hint } = this.state;
|
||||||
const cleanText = this.languageProvider ? this.languageProvider.cleanText : undefined;
|
const cleanText = languageProvider ? languageProvider.cleanText : undefined;
|
||||||
const chooserText = getChooserText(syntaxLoaded, metricsOptions);
|
const chooserText = getChooserText(syntaxLoaded, metricsOptions);
|
||||||
const buttonDisabled = !(syntaxLoaded && metricsOptions && metricsOptions.length > 0);
|
const buttonDisabled = !(syntaxLoaded && metricsOptions && metricsOptions.length > 0);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user