mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Prometheus: Disable exemplar queries for alerting (#41607)
* Prometheus: Dont include empty exempalr frame in results * Prometheus: Never run exemplar queries for alerting * Remove exemplar field from alerting and set exemplar to false * Add tests for frontend * Add test for backend
This commit is contained in:
parent
44837fc592
commit
9b9e41e036
@ -9,6 +9,7 @@ import { PluginMeta, GrafanaPlugin, PluginIncludeType } from './plugin';
|
||||
* */
|
||||
export enum CoreApp {
|
||||
CloudAlerting = 'cloud-alerting',
|
||||
UnifiedAlerting = 'unified-alerting',
|
||||
Dashboard = 'dashboard',
|
||||
Explore = 'explore',
|
||||
Unknown = 'unknown',
|
||||
|
@ -195,6 +195,12 @@ func (s *Service) parseTimeSeriesQuery(queryContext *backend.QueryDataRequest, d
|
||||
rangeQuery = true
|
||||
}
|
||||
|
||||
// We never want to run exemplar query for alerting
|
||||
exemplarQuery := model.ExemplarQuery
|
||||
if queryContext.Headers["FromAlert"] == "true" {
|
||||
exemplarQuery = false
|
||||
}
|
||||
|
||||
qs = append(qs, &PrometheusQuery{
|
||||
Expr: expr,
|
||||
Step: interval,
|
||||
@ -204,7 +210,7 @@ func (s *Service) parseTimeSeriesQuery(queryContext *backend.QueryDataRequest, d
|
||||
RefId: query.RefID,
|
||||
InstantQuery: model.InstantQuery,
|
||||
RangeQuery: rangeQuery,
|
||||
ExemplarQuery: model.ExemplarQuery,
|
||||
ExemplarQuery: exemplarQuery,
|
||||
UtcOffsetSec: model.UtcOffsetSec,
|
||||
})
|
||||
}
|
||||
|
@ -59,6 +59,37 @@ func TestPrometheus_timeSeriesQuery_parseTimeSeriesQuery(t *testing.T) {
|
||||
intervalCalculator: intervalv2.NewCalculator(),
|
||||
}
|
||||
|
||||
t.Run("parsing query from unified alerting", func(t *testing.T) {
|
||||
timeRange := backend.TimeRange{
|
||||
From: now,
|
||||
To: now.Add(12 * time.Hour),
|
||||
}
|
||||
|
||||
queryJson := `{
|
||||
"expr": "go_goroutines",
|
||||
"refId": "A",
|
||||
"exemplar": true
|
||||
}`
|
||||
|
||||
query := &backend.QueryDataRequest{
|
||||
Queries: []backend.DataQuery{
|
||||
{
|
||||
JSON: []byte(queryJson),
|
||||
TimeRange: timeRange,
|
||||
RefID: "A",
|
||||
},
|
||||
},
|
||||
Headers: map[string]string{
|
||||
"FromAlert": "true",
|
||||
},
|
||||
}
|
||||
|
||||
dsInfo := &DatasourceInfo{}
|
||||
models, err := service.parseTimeSeriesQuery(query, dsInfo)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, models[0].ExemplarQuery)
|
||||
})
|
||||
|
||||
t.Run("parsing query model with step", func(t *testing.T) {
|
||||
timeRange := backend.TimeRange{
|
||||
From: now,
|
||||
|
@ -2,6 +2,7 @@ import React, { FC, ReactNode, useState } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import {
|
||||
CoreApp,
|
||||
DataQuery,
|
||||
DataSourceInstanceSettings,
|
||||
getDefaultRelativeTimeRange,
|
||||
@ -83,6 +84,7 @@ export const QueryWrapper: FC<Props> = ({
|
||||
onRunQuery={onRunQueries}
|
||||
queries={queries}
|
||||
renderHeaderExtras={() => renderTimePicker(query, index)}
|
||||
app={CoreApp.UnifiedAlerting}
|
||||
visualization={
|
||||
data.state !== LoadingState.NotStarted ? (
|
||||
<VizWrapper
|
||||
|
@ -10,9 +10,10 @@ interface Props {
|
||||
onChange: (exemplar: boolean) => void;
|
||||
datasource: PrometheusDatasource;
|
||||
query: PromQuery;
|
||||
'data-testid'?: string;
|
||||
}
|
||||
|
||||
export function PromExemplarField({ datasource, onChange, query }: Props) {
|
||||
export function PromExemplarField({ datasource, onChange, query, ...rest }: Props) {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const styles = useStyles2(getStyles);
|
||||
const prevError = usePrevious(error);
|
||||
@ -41,7 +42,7 @@ export function PromExemplarField({ datasource, onChange, query }: Props) {
|
||||
);
|
||||
|
||||
return (
|
||||
<InlineLabel width="auto">
|
||||
<InlineLabel width="auto" data-testid={rest['data-testid']}>
|
||||
<Tooltip content={error ?? ''}>
|
||||
<div className={styles.iconWrapper}>
|
||||
Exemplars
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { dateTime } from '@grafana/data';
|
||||
import { dateTime, CoreApp } from '@grafana/data';
|
||||
import { screen, render } from '@testing-library/react';
|
||||
|
||||
import { PromQueryEditor } from './PromQueryEditor';
|
||||
import { PromQueryEditor, testIds } from './PromQueryEditor';
|
||||
import { PrometheusDatasource } from '../datasource';
|
||||
import { PromQuery } from '../types';
|
||||
|
||||
@ -17,10 +17,24 @@ jest.mock('app/features/dashboard/services/TimeSrv', () => {
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('./monaco-query-field/MonacoQueryFieldWrapper', () => {
|
||||
const fakeQueryField = () => <div>prometheus query field</div>;
|
||||
return {
|
||||
MonacoQueryFieldWrapper: fakeQueryField,
|
||||
};
|
||||
});
|
||||
|
||||
const setup = (propOverrides?: object) => {
|
||||
const datasourceMock: unknown = {
|
||||
createQuery: jest.fn((q) => q),
|
||||
getPrometheusTime: jest.fn((date, roundup) => 123),
|
||||
languageProvider: {
|
||||
start: () => Promise.resolve([]),
|
||||
syntax: () => {},
|
||||
getLabelKeys: () => [],
|
||||
metrics: [],
|
||||
},
|
||||
getInitHints: () => [],
|
||||
};
|
||||
const datasource: PrometheusDatasource = datasourceMock as PrometheusDatasource;
|
||||
const onRunQuery = jest.fn();
|
||||
@ -36,18 +50,24 @@ const setup = (propOverrides?: object) => {
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
|
||||
const wrapper = shallow(<PromQueryEditor {...props} />);
|
||||
const instance = wrapper.instance() as PromQueryEditor;
|
||||
|
||||
return {
|
||||
instance,
|
||||
wrapper,
|
||||
};
|
||||
return render(<PromQueryEditor {...props} />);
|
||||
};
|
||||
|
||||
describe('Render PromQueryEditor with basic options', () => {
|
||||
it('should render', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
it('should render editor', () => {
|
||||
setup();
|
||||
expect(screen.getByTestId(testIds.editor)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render exemplar editor for dashboard', () => {
|
||||
setup({ app: CoreApp.Dashboard });
|
||||
expect(screen.getByTestId(testIds.editor)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(testIds.exemplar)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render exemplar editor for unified alerting', () => {
|
||||
setup({ app: CoreApp.UnifiedAlerting });
|
||||
expect(screen.getByTestId(testIds.editor)).toBeInTheDocument();
|
||||
expect(screen.queryByTestId(testIds.exemplar)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -3,7 +3,7 @@ import React, { PureComponent } from 'react';
|
||||
|
||||
// Types
|
||||
import { InlineFormLabel, LegacyForms, Select } from '@grafana/ui';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { CoreApp, SelectableValue } from '@grafana/data';
|
||||
import { PromQuery } from '../types';
|
||||
|
||||
import PromQueryField from './PromQueryField';
|
||||
@ -44,7 +44,8 @@ export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State>
|
||||
expr: '',
|
||||
legendFormat: '',
|
||||
interval: '',
|
||||
exemplar: true,
|
||||
// Set exemplar to false for alerting queries
|
||||
exemplar: props.app === CoreApp.UnifiedAlerting ? false : true,
|
||||
};
|
||||
const query = Object.assign({}, defaultQuery, props.query);
|
||||
this.query = query;
|
||||
@ -111,6 +112,8 @@ export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State>
|
||||
render() {
|
||||
const { datasource, query, range, data } = this.props;
|
||||
const { formatOption, instant, interval, intervalFactorOption, legendFormat } = this.state;
|
||||
//We want to hide exemplar field for unified alerting as exemplars in alerting don't make sense and are source of confusion
|
||||
const showExemplarField = this.props.app !== CoreApp.UnifiedAlerting;
|
||||
|
||||
return (
|
||||
<PromQueryField
|
||||
@ -197,7 +200,14 @@ export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State>
|
||||
/>
|
||||
</InlineFormLabel>
|
||||
</div>
|
||||
<PromExemplarField onChange={this.onExemplarChange} datasource={datasource} query={this.query} />
|
||||
{showExemplarField && (
|
||||
<PromExemplarField
|
||||
onChange={this.onExemplarChange}
|
||||
datasource={datasource}
|
||||
query={this.query}
|
||||
data-testid={testIds.exemplar}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
@ -207,4 +217,5 @@ export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State>
|
||||
|
||||
export const testIds = {
|
||||
editor: 'prom-editor',
|
||||
exemplar: 'exemplar-editor',
|
||||
};
|
||||
|
@ -1,209 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Render PromQueryEditor with basic options should render 1`] = `
|
||||
<PromQueryField
|
||||
ExtraFieldElement={
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
tooltip="Controls the name of the time series, using name or pattern. For example
|
||||
{{hostname}} will be replaced with label value for the label hostname."
|
||||
width={7}
|
||||
>
|
||||
Legend
|
||||
</FormLabel>
|
||||
<input
|
||||
className="gf-form-input"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
placeholder="legend format"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
tooltip={
|
||||
<React.Fragment>
|
||||
An additional lower limit for the step parameter of the Prometheus query and for the
|
||||
|
||||
<code>
|
||||
$__interval
|
||||
</code>
|
||||
and
|
||||
<code>
|
||||
$__rate_interval
|
||||
</code>
|
||||
variables. The limit is absolute and not modified by the "Resolution" setting.
|
||||
</React.Fragment>
|
||||
}
|
||||
width={7}
|
||||
>
|
||||
Min step
|
||||
</FormLabel>
|
||||
<input
|
||||
className="gf-form-input width-8"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<div
|
||||
className="gf-form-label"
|
||||
>
|
||||
Resolution
|
||||
</div>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
menuShouldPortal={true}
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"label": "1/1",
|
||||
"value": 1,
|
||||
},
|
||||
Object {
|
||||
"label": "1/2",
|
||||
"value": 2,
|
||||
},
|
||||
Object {
|
||||
"label": "1/3",
|
||||
"value": 3,
|
||||
},
|
||||
Object {
|
||||
"label": "1/4",
|
||||
"value": 4,
|
||||
},
|
||||
Object {
|
||||
"label": "1/5",
|
||||
"value": 5,
|
||||
},
|
||||
Object {
|
||||
"label": "1/10",
|
||||
"value": 10,
|
||||
},
|
||||
]
|
||||
}
|
||||
value={
|
||||
Object {
|
||||
"label": "1/1",
|
||||
"value": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<div
|
||||
className="gf-form-label width-7"
|
||||
>
|
||||
Format
|
||||
</div>
|
||||
<Select
|
||||
className="select-container"
|
||||
isSearchable={false}
|
||||
menuShouldPortal={true}
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"label": "Time series",
|
||||
"value": "time_series",
|
||||
},
|
||||
Object {
|
||||
"label": "Table",
|
||||
"value": "table",
|
||||
},
|
||||
Object {
|
||||
"label": "Heatmap",
|
||||
"value": "heatmap",
|
||||
},
|
||||
]
|
||||
}
|
||||
value={
|
||||
Object {
|
||||
"label": "Time series",
|
||||
"value": "time_series",
|
||||
}
|
||||
}
|
||||
width={16}
|
||||
/>
|
||||
<Switch
|
||||
checked={false}
|
||||
label="Instant"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<FormLabel
|
||||
tooltip="Link to Graph in Prometheus"
|
||||
width={10}
|
||||
>
|
||||
<Memo(PromLink)
|
||||
datasource={
|
||||
Object {
|
||||
"createQuery": [MockFunction],
|
||||
"getPrometheusTime": [MockFunction],
|
||||
}
|
||||
}
|
||||
query={
|
||||
Object {
|
||||
"exemplar": true,
|
||||
"expr": "",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</FormLabel>
|
||||
</div>
|
||||
<PromExemplarField
|
||||
datasource={
|
||||
Object {
|
||||
"createQuery": [MockFunction],
|
||||
"getPrometheusTime": [MockFunction],
|
||||
}
|
||||
}
|
||||
onChange={[Function]}
|
||||
query={
|
||||
Object {
|
||||
"exemplar": true,
|
||||
"expr": "",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
data-testid="prom-editor"
|
||||
datasource={
|
||||
Object {
|
||||
"createQuery": [MockFunction],
|
||||
"getPrometheusTime": [MockFunction],
|
||||
}
|
||||
}
|
||||
history={Array []}
|
||||
onChange={[Function]}
|
||||
onRunQuery={[Function]}
|
||||
query={
|
||||
Object {
|
||||
"expr": "",
|
||||
"refId": "A",
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
Loading…
Reference in New Issue
Block a user