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
pull/41638/head
Ivana Huckova 4 years ago committed by GitHub
parent 44837fc592
commit 9b9e41e036
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      packages/grafana-data/src/types/app.ts
  2. 8
      pkg/tsdb/prometheus/time_series_query.go
  3. 31
      pkg/tsdb/prometheus/time_series_query_test.go
  4. 2
      public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx
  5. 5
      public/app/plugins/datasource/prometheus/components/PromExemplarField.tsx
  6. 46
      public/app/plugins/datasource/prometheus/components/PromQueryEditor.test.tsx
  7. 17
      public/app/plugins/datasource/prometheus/components/PromQueryEditor.tsx
  8. 209
      public/app/plugins/datasource/prometheus/components/__snapshots__/PromQueryEditor.test.tsx.snap

@ -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…
Cancel
Save