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'; ...@@ -4,6 +4,9 @@ import RCCascader from 'rc-cascader';
import React from 'react'; import React from 'react';
import PromQlLanguageProvider, { DEFAULT_LOOKUP_METRICS_THRESHOLD } from '../language_provider'; import PromQlLanguageProvider, { DEFAULT_LOOKUP_METRICS_THRESHOLD } from '../language_provider';
import PromQueryField, { groupMetricsByPrefix, RECORDING_RULES_GROUP } from './PromQueryField'; import PromQueryField, { groupMetricsByPrefix, RECORDING_RULES_GROUP } from './PromQueryField';
import { ButtonCascader } from '@grafana/ui';
import { DataSourceInstanceSettings } from '@grafana/data';
import { PromOptions } from '../types';
describe('PromQueryField', () => { describe('PromQueryField', () => {
beforeAll(() => { beforeAll(() => {
...@@ -11,6 +14,47 @@ describe('PromQueryField', () => { ...@@ -11,6 +14,47 @@ describe('PromQueryField', () => {
window.getSelection = () => {}; 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 () => { it('refreshes metrics when the data source changes', async () => {
const metrics = ['foo', 'bar']; const metrics = ['foo', 'bar'];
const languageProvider = ({ const languageProvider = ({
......
...@@ -25,13 +25,19 @@ const HISTOGRAM_GROUP = '__histograms__'; ...@@ -25,13 +25,19 @@ const HISTOGRAM_GROUP = '__histograms__';
const PRISM_SYNTAX = 'promql'; const PRISM_SYNTAX = 'promql';
export const RECORDING_RULES_GROUP = '__recording_rules__'; 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) { if (!hasSyntax) {
return 'Loading metrics...'; return 'Loading metrics...';
} }
if (metrics && metrics.length === 0) { if (metrics && metrics.length === 0) {
return '(No metrics found)'; return '(No metrics found)';
} }
return 'Metrics'; return 'Metrics';
} }
...@@ -250,12 +256,10 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF ...@@ -250,12 +256,10 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
onUpdateLanguage = () => { onUpdateLanguage = () => {
const { const {
histogramMetrics, datasource,
metrics, datasource: { languageProvider },
metricsMetadata, } = this.props;
lookupsDisabled, const { histogramMetrics, metrics, metricsMetadata, lookupMetricsThreshold } = languageProvider;
lookupMetricsThreshold,
} = this.props.datasource.languageProvider;
if (!metrics) { if (!metrics) {
return; return;
...@@ -274,7 +278,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF ...@@ -274,7 +278,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
// Hint for big disabled lookups // Hint for big disabled lookups
let hint: QueryHint; let hint: QueryHint;
if (lookupsDisabled) { if (!datasource.lookupsDisabled && languageProvider.lookupsDisabled) {
hint = { hint = {
label: `Dynamic label lookup is disabled for datasources with more than ${lookupMetricsThreshold} metrics.`, label: `Dynamic label lookup is disabled for datasources with more than ${lookupMetricsThreshold} metrics.`,
type: 'INFO', type: 'INFO',
...@@ -308,13 +312,14 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF ...@@ -308,13 +312,14 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
render() { render() {
const { const {
datasource,
datasource: { languageProvider }, datasource: { languageProvider },
query, query,
ExtraFieldElement, ExtraFieldElement,
} = this.props; } = this.props;
const { metricsOptions, syntaxLoaded, hint } = this.state; const { metricsOptions, syntaxLoaded, hint } = this.state;
const cleanText = languageProvider ? languageProvider.cleanText : undefined; 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); const buttonDisabled = !(syntaxLoaded && metricsOptions && metricsOptions.length > 0);
return ( return (
......
...@@ -16,7 +16,7 @@ export const ConfigEditor = (props: Props) => { ...@@ -16,7 +16,7 @@ export const ConfigEditor = (props: Props) => {
onChange={onOptionsChange} onChange={onOptionsChange}
/> />
<PromSettings value={options} onChange={onOptionsChange} /> <PromSettings options={options} onOptionsChange={onOptionsChange} />
</> </>
); );
}; };
import React, { SyntheticEvent } from 'react'; import React, { SyntheticEvent } from 'react';
import { EventsWithValidation, InlineFormLabel, regexValidation, LegacyForms } from '@grafana/ui'; import { EventsWithValidation, InlineFormLabel, regexValidation, LegacyForms } from '@grafana/ui';
const { Select, Input, FormField } = LegacyForms; const { Select, Input, FormField, Switch } = LegacyForms;
import { DataSourceSettings, SelectableValue } from '@grafana/data'; import {
SelectableValue,
onUpdateDatasourceJsonDataOptionChecked,
DataSourcePluginOptionsEditorProps,
} from '@grafana/data';
import { PromOptions } from '../types'; import { PromOptions } from '../types';
const httpOptions = [ const httpOptions = [
...@@ -9,13 +13,10 @@ const httpOptions = [ ...@@ -9,13 +13,10 @@ const httpOptions = [
{ value: 'POST', label: 'POST' }, { value: 'POST', label: 'POST' },
]; ];
type Props = { type Props = Pick<DataSourcePluginOptionsEditorProps<PromOptions>, 'options' | 'onOptionsChange'>;
value: DataSourceSettings<PromOptions>;
onChange: (value: DataSourceSettings<PromOptions>) => void;
};
export const PromSettings = (props: Props) => { export const PromSettings = (props: Props) => {
const { value, onChange } = props; const { options, onOptionsChange } = props;
return ( return (
<> <>
...@@ -28,10 +29,10 @@ export const PromSettings = (props: Props) => { ...@@ -28,10 +29,10 @@ export const PromSettings = (props: Props) => {
inputEl={ inputEl={
<Input <Input
className="width-6" className="width-6"
value={value.jsonData.timeInterval} value={options.jsonData.timeInterval}
spellCheck={false} spellCheck={false}
placeholder="15s" placeholder="15s"
onChange={onChangeHandler('timeInterval', value, onChange)} onChange={onChangeHandler('timeInterval', options, onOptionsChange)}
validationEvents={promSettingsValidationEvents} validationEvents={promSettingsValidationEvents}
/> />
} }
...@@ -47,8 +48,8 @@ export const PromSettings = (props: Props) => { ...@@ -47,8 +48,8 @@ export const PromSettings = (props: Props) => {
inputEl={ inputEl={
<Input <Input
className="width-6" className="width-6"
value={value.jsonData.queryTimeout} value={options.jsonData.queryTimeout}
onChange={onChangeHandler('queryTimeout', value, onChange)} onChange={onChangeHandler('queryTimeout', options, onOptionsChange)}
spellCheck={false} spellCheck={false}
placeholder="60s" placeholder="60s"
validationEvents={promSettingsValidationEvents} validationEvents={promSettingsValidationEvents}
...@@ -67,14 +68,23 @@ export const PromSettings = (props: Props) => { ...@@ -67,14 +68,23 @@ export const PromSettings = (props: Props) => {
</InlineFormLabel> </InlineFormLabel>
<Select <Select
options={httpOptions} options={httpOptions}
value={httpOptions.find(o => o.value === value.jsonData.httpMethod)} value={httpOptions.find(o => o.value === options.jsonData.httpMethod)}
onChange={onChangeHandler('httpMethod', value, onChange)} onChange={onChangeHandler('httpMethod', options, onOptionsChange)}
width={7} width={7}
/> />
</div> </div>
</div> </div>
<h3 className="page-heading">Misc</h3> <h3 className="page-heading">Misc</h3>
<div className="gf-form-group"> <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-inline">
<div className="gf-form max-width-30"> <div className="gf-form max-width-30">
<FormField <FormField
...@@ -84,8 +94,8 @@ export const PromSettings = (props: Props) => { ...@@ -84,8 +94,8 @@ export const PromSettings = (props: Props) => {
inputEl={ inputEl={
<Input <Input
className="width-25" className="width-25"
value={value.jsonData.customQueryParameters} value={options.jsonData.customQueryParameters}
onChange={onChangeHandler('customQueryParameters', value, onChange)} onChange={onChangeHandler('customQueryParameters', options, onOptionsChange)}
spellCheck={false} spellCheck={false}
placeholder="Example: max_source_resolution=5m&timeout=10" placeholder="Example: max_source_resolution=5m&timeout=10"
/> />
...@@ -119,13 +129,15 @@ export const getValueFromEventItem = (eventItem: SyntheticEvent<HTMLInputElement ...@@ -119,13 +129,15 @@ export const getValueFromEventItem = (eventItem: SyntheticEvent<HTMLInputElement
return (eventItem as SelectableValue<string>).value; return (eventItem as SelectableValue<string>).value;
}; };
const onChangeHandler = (key: keyof PromOptions, value: Props['value'], onChange: Props['onChange']) => ( const onChangeHandler = (
eventItem: SyntheticEvent<HTMLInputElement> | SelectableValue<string> key: keyof PromOptions,
) => { options: Props['options'],
onChange({ onOptionsChange: Props['onOptionsChange']
...value, ) => (eventItem: SyntheticEvent<HTMLInputElement> | SelectableValue<string>) => {
onOptionsChange({
...options,
jsonData: { jsonData: {
...value.jsonData, ...options.jsonData,
[key]: getValueFromEventItem(eventItem), [key]: getValueFromEventItem(eventItem),
}, },
}); });
......
...@@ -83,6 +83,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions> ...@@ -83,6 +83,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
queryTimeout: string; queryTimeout: string;
httpMethod: string; httpMethod: string;
languageProvider: PrometheusLanguageProvider; languageProvider: PrometheusLanguageProvider;
lookupsDisabled: boolean;
resultTransformer: ResultTransformer; resultTransformer: ResultTransformer;
customQueryParameters: any; customQueryParameters: any;
...@@ -101,6 +102,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions> ...@@ -101,6 +102,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
this.resultTransformer = new ResultTransformer(templateSrv); this.resultTransformer = new ResultTransformer(templateSrv);
this.ruleMappings = {}; this.ruleMappings = {};
this.languageProvider = new PrometheusLanguageProvider(this); this.languageProvider = new PrometheusLanguageProvider(this);
this.lookupsDisabled = instanceSettings.jsonData.disableMetricsLookup;
this.customQueryParameters = new URLSearchParams(instanceSettings.jsonData.customQueryParameters); this.customQueryParameters = new URLSearchParams(instanceSettings.jsonData.customQueryParameters);
} }
......
...@@ -112,10 +112,15 @@ export default class PromQlLanguageProvider extends LanguageProvider { ...@@ -112,10 +112,15 @@ export default class PromQlLanguageProvider extends LanguageProvider {
}; };
start = async (): Promise<any[]> => { start = async (): Promise<any[]> => {
if (this.datasource.lookupsDisabled) {
return [];
}
this.metrics = await this.request('/api/v1/label/__name__/values', []); this.metrics = await this.request('/api/v1/label/__name__/values', []);
this.lookupsDisabled = this.metrics.length > this.lookupMetricsThreshold; this.lookupsDisabled = this.metrics.length > this.lookupMetricsThreshold;
this.metricsMetadata = await this.request('/api/v1/metadata', {}); this.metricsMetadata = await this.request('/api/v1/metadata', {});
this.processHistogramMetrics(this.metrics); this.processHistogramMetrics(this.metrics);
return []; return [];
}; };
......
...@@ -20,6 +20,7 @@ export interface PromOptions extends DataSourceJsonData { ...@@ -20,6 +20,7 @@ export interface PromOptions extends DataSourceJsonData {
httpMethod: string; httpMethod: string;
directUrl: string; directUrl: string;
customQueryParameters?: string; customQueryParameters?: string;
disableMetricsLookup?: boolean;
} }
export interface PromQueryRequest extends PromQuery { 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