Commit de0e1b2c by Andreas Opferkuch Committed by GitHub

Prometheus: Add off switch for metric/label name lookup (#24034)

* Prometheus: Add off switch for metric/label name lookup

This will help users with amounts of metric name
data that is too much for a browser to handle.

Autocomplete will be disabled and metrics chooser hidden,
since obviously both rely on this data.

Fixes #22702

* Use onUpdateDatasourceJsonDataOptionChecked

... from `@grafana/data`. Refactor naming to faciliate its
use and stick with prop names as passed down from
`ConfigEditor`.

PLUS:
- Rephrase switch label, add a tooltip and reduce the
size of the to what "Custom query parameters" originally
was.
- Change `languageProvider` type in `PromQueryField`.

* Put language provider back in

Functions and history still work, even when metrics
lookup gets disabled.
Also: Rewording of setting.

* Display a message when lookup got disabled manually

* Call property for setting disableMetricsLookup

* Show disabled metrics chooser instead of warning
parent 42ba13b3
......@@ -4,6 +4,9 @@ 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';
import { ButtonCascader } from '@grafana/ui';
import { DataSourceInstanceSettings } from '@grafana/data';
import { PromOptions } from '../types';
describe('PromQueryField', () => {
beforeAll(() => {
......@@ -11,6 +14,47 @@ describe('PromQueryField', () => {
window.getSelection = () => {};
});
it('renders metrics chooser regularly if lookups are not disabled in the datasource settings', () => {
const datasource = ({
languageProvider: {
start: () => Promise.resolve([]),
},
} as unknown) as DataSourceInstanceSettings<PromOptions>;
const queryField = mount(
<PromQueryField
// @ts-ignore
datasource={datasource}
query={{ expr: '', refId: '' }}
onRunQuery={() => {}}
onChange={() => {}}
history={[]}
/>
);
expect(queryField.find(ButtonCascader).length).toBe(1);
});
it('renders a disabled metrics chooser if lookups are disabled in datasource settings', () => {
const queryField = mount(
<PromQueryField
// @ts-ignore
datasource={{ lookupsDisabled: true }}
query={{ expr: '', refId: '' }}
onRunQuery={() => {}}
onChange={() => {}}
history={[]}
/>
);
expect(
queryField
.find(ButtonCascader)
.find('button')
.props().disabled
).toBe(true);
});
it('refreshes metrics when the data source changes', async () => {
const metrics = ['foo', 'bar'];
const languageProvider = ({
......
......@@ -25,13 +25,19 @@ const HISTOGRAM_GROUP = '__histograms__';
const PRISM_SYNTAX = 'promql';
export const RECORDING_RULES_GROUP = '__recording_rules__';
function getChooserText(hasSyntax: boolean, metrics: string[]) {
function getChooserText(metricsLookupDisabled: boolean, hasSyntax: boolean, metrics: string[]) {
if (metricsLookupDisabled) {
return '(Disabled)';
}
if (!hasSyntax) {
return 'Loading metrics...';
}
if (metrics && metrics.length === 0) {
return '(No metrics found)';
}
return 'Metrics';
}
......@@ -250,12 +256,10 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
onUpdateLanguage = () => {
const {
histogramMetrics,
metrics,
metricsMetadata,
lookupsDisabled,
lookupMetricsThreshold,
} = this.props.datasource.languageProvider;
datasource,
datasource: { languageProvider },
} = this.props;
const { histogramMetrics, metrics, metricsMetadata, lookupMetricsThreshold } = languageProvider;
if (!metrics) {
return;
......@@ -274,7 +278,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
// Hint for big disabled lookups
let hint: QueryHint;
if (lookupsDisabled) {
if (!datasource.lookupsDisabled && languageProvider.lookupsDisabled) {
hint = {
label: `Dynamic label lookup is disabled for datasources with more than ${lookupMetricsThreshold} metrics.`,
type: 'INFO',
......@@ -308,13 +312,14 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
render() {
const {
datasource,
datasource: { languageProvider },
query,
ExtraFieldElement,
} = this.props;
const { metricsOptions, syntaxLoaded, hint } = this.state;
const cleanText = languageProvider ? languageProvider.cleanText : undefined;
const chooserText = getChooserText(syntaxLoaded, metricsOptions);
const chooserText = getChooserText(datasource.lookupsDisabled, syntaxLoaded, metricsOptions);
const buttonDisabled = !(syntaxLoaded && metricsOptions && metricsOptions.length > 0);
return (
......
......@@ -16,7 +16,7 @@ export const ConfigEditor = (props: Props) => {
onChange={onOptionsChange}
/>
<PromSettings value={options} onChange={onOptionsChange} />
<PromSettings options={options} onOptionsChange={onOptionsChange} />
</>
);
};
import React, { SyntheticEvent } from 'react';
import { EventsWithValidation, InlineFormLabel, regexValidation, LegacyForms } from '@grafana/ui';
const { Select, Input, FormField } = LegacyForms;
import { DataSourceSettings, SelectableValue } from '@grafana/data';
const { Select, Input, FormField, Switch } = LegacyForms;
import {
SelectableValue,
onUpdateDatasourceJsonDataOptionChecked,
DataSourcePluginOptionsEditorProps,
} from '@grafana/data';
import { PromOptions } from '../types';
const httpOptions = [
......@@ -9,13 +13,10 @@ const httpOptions = [
{ value: 'POST', label: 'POST' },
];
type Props = {
value: DataSourceSettings<PromOptions>;
onChange: (value: DataSourceSettings<PromOptions>) => void;
};
type Props = Pick<DataSourcePluginOptionsEditorProps<PromOptions>, 'options' | 'onOptionsChange'>;
export const PromSettings = (props: Props) => {
const { value, onChange } = props;
const { options, onOptionsChange } = props;
return (
<>
......@@ -28,10 +29,10 @@ export const PromSettings = (props: Props) => {
inputEl={
<Input
className="width-6"
value={value.jsonData.timeInterval}
value={options.jsonData.timeInterval}
spellCheck={false}
placeholder="15s"
onChange={onChangeHandler('timeInterval', value, onChange)}
onChange={onChangeHandler('timeInterval', options, onOptionsChange)}
validationEvents={promSettingsValidationEvents}
/>
}
......@@ -47,8 +48,8 @@ export const PromSettings = (props: Props) => {
inputEl={
<Input
className="width-6"
value={value.jsonData.queryTimeout}
onChange={onChangeHandler('queryTimeout', value, onChange)}
value={options.jsonData.queryTimeout}
onChange={onChangeHandler('queryTimeout', options, onOptionsChange)}
spellCheck={false}
placeholder="60s"
validationEvents={promSettingsValidationEvents}
......@@ -67,14 +68,23 @@ export const PromSettings = (props: Props) => {
</InlineFormLabel>
<Select
options={httpOptions}
value={httpOptions.find(o => o.value === value.jsonData.httpMethod)}
onChange={onChangeHandler('httpMethod', value, onChange)}
value={httpOptions.find(o => o.value === options.jsonData.httpMethod)}
onChange={onChangeHandler('httpMethod', options, onOptionsChange)}
width={7}
/>
</div>
</div>
<h3 className="page-heading">Misc</h3>
<div className="gf-form-group">
<div className="gf-form">
<Switch
checked={options.jsonData.disableMetricsLookup}
label="Disable metrics lookup"
labelClass="width-14"
onChange={onUpdateDatasourceJsonDataOptionChecked(props, 'disableMetricsLookup')}
tooltip="Checking this option will disable the metrics chooser and metric/label support in the query field's autocomplete. This helps if you have performance issues with bigger Prometheus instances."
/>
</div>
<div className="gf-form-inline">
<div className="gf-form max-width-30">
<FormField
......@@ -84,8 +94,8 @@ export const PromSettings = (props: Props) => {
inputEl={
<Input
className="width-25"
value={value.jsonData.customQueryParameters}
onChange={onChangeHandler('customQueryParameters', value, onChange)}
value={options.jsonData.customQueryParameters}
onChange={onChangeHandler('customQueryParameters', options, onOptionsChange)}
spellCheck={false}
placeholder="Example: max_source_resolution=5m&timeout=10"
/>
......@@ -119,13 +129,15 @@ export const getValueFromEventItem = (eventItem: SyntheticEvent<HTMLInputElement
return (eventItem as SelectableValue<string>).value;
};
const onChangeHandler = (key: keyof PromOptions, value: Props['value'], onChange: Props['onChange']) => (
eventItem: SyntheticEvent<HTMLInputElement> | SelectableValue<string>
) => {
onChange({
...value,
const onChangeHandler = (
key: keyof PromOptions,
options: Props['options'],
onOptionsChange: Props['onOptionsChange']
) => (eventItem: SyntheticEvent<HTMLInputElement> | SelectableValue<string>) => {
onOptionsChange({
...options,
jsonData: {
...value.jsonData,
...options.jsonData,
[key]: getValueFromEventItem(eventItem),
},
});
......
......@@ -83,6 +83,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
queryTimeout: string;
httpMethod: string;
languageProvider: PrometheusLanguageProvider;
lookupsDisabled: boolean;
resultTransformer: ResultTransformer;
customQueryParameters: any;
......@@ -101,6 +102,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
this.resultTransformer = new ResultTransformer(templateSrv);
this.ruleMappings = {};
this.languageProvider = new PrometheusLanguageProvider(this);
this.lookupsDisabled = instanceSettings.jsonData.disableMetricsLookup;
this.customQueryParameters = new URLSearchParams(instanceSettings.jsonData.customQueryParameters);
}
......
......@@ -112,10 +112,15 @@ export default class PromQlLanguageProvider extends LanguageProvider {
};
start = async (): Promise<any[]> => {
if (this.datasource.lookupsDisabled) {
return [];
}
this.metrics = await this.request('/api/v1/label/__name__/values', []);
this.lookupsDisabled = this.metrics.length > this.lookupMetricsThreshold;
this.metricsMetadata = await this.request('/api/v1/metadata', {});
this.processHistogramMetrics(this.metrics);
return [];
};
......
......@@ -20,6 +20,7 @@ export interface PromOptions extends DataSourceJsonData {
httpMethod: string;
directUrl: string;
customQueryParameters?: string;
disableMetricsLookup?: boolean;
}
export interface PromQueryRequest extends PromQuery {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment