Exemplars: always query exemplars (#31673)
* Exemplars: always query exemplars * Update exemplar button to be an eye * Add tooltip to eye button
This commit is contained in:
@@ -1,21 +1,68 @@
|
|||||||
import { InlineField, InlineSwitch } from '@grafana/ui';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
import React from 'react';
|
import { FetchError } from '@grafana/runtime';
|
||||||
|
import { IconButton, InlineLabel, Tooltip, useStyles } from '@grafana/ui';
|
||||||
|
import { css, cx } from 'emotion';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { PrometheusDatasource } from '../datasource';
|
||||||
import { PromQuery } from '../types';
|
import { PromQuery } from '../types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
query: PromQuery;
|
query: PromQuery;
|
||||||
onChange: (value: PromQuery) => void;
|
onChange: (value: PromQuery) => void;
|
||||||
|
datasource: PrometheusDatasource;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onExemplarsChange = ({ query, onChange }: Props) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const exemplar = e.target.checked;
|
|
||||||
onChange({ ...query, exemplar });
|
|
||||||
};
|
|
||||||
|
|
||||||
export function PromExemplarField(props: Props) {
|
export function PromExemplarField(props: Props) {
|
||||||
|
const [error, setError] = useState<FetchError>();
|
||||||
|
const styles = useStyles(getStyles);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const subscription = props.datasource.exemplarErrors.subscribe((err) => {
|
||||||
|
setError(err);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
};
|
||||||
|
}, [props]);
|
||||||
|
|
||||||
|
const iconButtonStyles = cx(
|
||||||
|
{
|
||||||
|
[styles.activeIcon]: !!props.query.exemplar,
|
||||||
|
},
|
||||||
|
styles.eyeIcon
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InlineField label="Exemplars" labelWidth="auto">
|
<InlineLabel width="auto">
|
||||||
<InlineSwitch label="Exemplars" value={!!props.query.exemplar} onChange={onExemplarsChange(props)} />
|
<Tooltip content={!!error ? 'Exemplars are not supported in this version of prometheus.' : ''}>
|
||||||
</InlineField>
|
<div className={styles.iconWrapper}>
|
||||||
|
Exemplars
|
||||||
|
<IconButton
|
||||||
|
name="eye"
|
||||||
|
tooltip={!!props.query.exemplar ? 'Disable query with exemplars' : 'Enable query with exemplars'}
|
||||||
|
disabled={!!error}
|
||||||
|
className={iconButtonStyles}
|
||||||
|
onClick={() => {
|
||||||
|
props.onChange({ ...props.query, exemplar: !props.query.exemplar });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</InlineLabel>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getStyles(theme: GrafanaTheme) {
|
||||||
|
return {
|
||||||
|
eyeIcon: css`
|
||||||
|
margin-left: ${theme.spacing.md};
|
||||||
|
`,
|
||||||
|
activeIcon: css`
|
||||||
|
color: ${theme.palette.blue95};
|
||||||
|
`,
|
||||||
|
iconWrapper: css`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { PromExploreExtraFieldProps, PromExploreExtraField } from './PromExploreExtraField';
|
import { PromExploreExtraFieldProps, PromExploreExtraField } from './PromExploreExtraField';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
const setup = (propOverrides?: PromExploreExtraFieldProps) => {
|
const setup = (propOverrides?: PromExploreExtraFieldProps) => {
|
||||||
const queryType = 'range';
|
const queryType = 'range';
|
||||||
const stepValue = '1';
|
const stepValue = '1';
|
||||||
const query = { exemplar: false };
|
const query = { exemplar: false };
|
||||||
|
const datasource = { exemplarErrors: new Observable() };
|
||||||
const onStepChange = jest.fn();
|
const onStepChange = jest.fn();
|
||||||
const onQueryTypeChange = jest.fn();
|
const onQueryTypeChange = jest.fn();
|
||||||
const onKeyDownFunc = jest.fn();
|
const onKeyDownFunc = jest.fn();
|
||||||
@@ -17,6 +19,7 @@ const setup = (propOverrides?: PromExploreExtraFieldProps) => {
|
|||||||
onStepChange,
|
onStepChange,
|
||||||
onQueryTypeChange,
|
onQueryTypeChange,
|
||||||
onKeyDownFunc,
|
onKeyDownFunc,
|
||||||
|
datasource,
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.assign(props, propOverrides);
|
Object.assign(props, propOverrides);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { css, cx } from 'emotion';
|
|||||||
import { InlineFormLabel, RadioButtonGroup } from '@grafana/ui';
|
import { InlineFormLabel, RadioButtonGroup } from '@grafana/ui';
|
||||||
import { PromQuery } from '../types';
|
import { PromQuery } from '../types';
|
||||||
import { PromExemplarField } from './PromExemplarField';
|
import { PromExemplarField } from './PromExemplarField';
|
||||||
|
import { PrometheusDatasource } from '../datasource';
|
||||||
|
|
||||||
export interface PromExploreExtraFieldProps {
|
export interface PromExploreExtraFieldProps {
|
||||||
queryType: string;
|
queryType: string;
|
||||||
@@ -15,10 +16,11 @@ export interface PromExploreExtraFieldProps {
|
|||||||
onKeyDownFunc: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
onKeyDownFunc: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||||
onQueryTypeChange: (value: string) => void;
|
onQueryTypeChange: (value: string) => void;
|
||||||
onChange: (value: PromQuery) => void;
|
onChange: (value: PromQuery) => void;
|
||||||
|
datasource: PrometheusDatasource;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PromExploreExtraField: React.FC<PromExploreExtraFieldProps> = memo(
|
export const PromExploreExtraField: React.FC<PromExploreExtraFieldProps> = memo(
|
||||||
({ queryType, stepValue, query, onChange, onStepChange, onQueryTypeChange, onKeyDownFunc }) => {
|
({ queryType, stepValue, query, onChange, onStepChange, onQueryTypeChange, onKeyDownFunc, datasource }) => {
|
||||||
const rangeOptions = [
|
const rangeOptions = [
|
||||||
{ value: 'range', label: 'Range', description: 'Run query over a range of time.' },
|
{ value: 'range', label: 'Range', description: 'Run query over a range of time.' },
|
||||||
{
|
{
|
||||||
@@ -75,7 +77,7 @@ export const PromExploreExtraField: React.FC<PromExploreExtraFieldProps> = memo(
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PromExemplarField query={query} onChange={onChange} />
|
<PromExemplarField query={query} onChange={onChange} datasource={datasource} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { memo, FC } from 'react';
|
import React, { memo, FC, useEffect } from 'react';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { ExploreQueryFieldProps } from '@grafana/data';
|
import { ExploreQueryFieldProps } from '@grafana/data';
|
||||||
@@ -14,6 +14,12 @@ export type Props = ExploreQueryFieldProps<PrometheusDatasource, PromQuery, Prom
|
|||||||
export const PromExploreQueryEditor: FC<Props> = (props: Props) => {
|
export const PromExploreQueryEditor: FC<Props> = (props: Props) => {
|
||||||
const { range, query, data, datasource, history, onChange, onRunQuery } = props;
|
const { range, query, data, datasource, history, onChange, onRunQuery } = props;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (query.exemplar === undefined) {
|
||||||
|
onChange({ ...query, exemplar: true });
|
||||||
|
}
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
function onChangeQueryStep(value: string) {
|
function onChangeQueryStep(value: string) {
|
||||||
const { query, onChange } = props;
|
const { query, onChange } = props;
|
||||||
const nextQuery = { ...query, interval: value };
|
const nextQuery = { ...query, interval: value };
|
||||||
@@ -65,6 +71,7 @@ export const PromExploreQueryEditor: FC<Props> = (props: Props) => {
|
|||||||
onKeyDownFunc={onReturnKeyDown}
|
onKeyDownFunc={onReturnKeyDown}
|
||||||
query={query}
|
query={query}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
datasource={datasource}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export class PromQueryEditor extends PureComponent<Props, State> {
|
|||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
// Use default query to prevent undefined input values
|
// Use default query to prevent undefined input values
|
||||||
const defaultQuery: Partial<PromQuery> = { expr: '', legendFormat: '', interval: '' };
|
const defaultQuery: Partial<PromQuery> = { expr: '', legendFormat: '', interval: '', exemplar: true };
|
||||||
const query = Object.assign({}, defaultQuery, props.query);
|
const query = Object.assign({}, defaultQuery, props.query);
|
||||||
this.query = query;
|
this.query = query;
|
||||||
// Query target properties that are fully controlled inputs
|
// Query target properties that are fully controlled inputs
|
||||||
@@ -186,7 +186,7 @@ export class PromQueryEditor extends PureComponent<Props, State> {
|
|||||||
</InlineFormLabel>
|
</InlineFormLabel>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PromExemplarField query={query} onChange={onChange} />
|
<PromExemplarField query={this.query} onChange={onChange} datasource={this.props.datasource} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
+7
@@ -4,6 +4,13 @@ exports[`PromExploreQueryEditor should render component 1`] = `
|
|||||||
<PromQueryField
|
<PromQueryField
|
||||||
ExtraFieldElement={
|
ExtraFieldElement={
|
||||||
<Memo
|
<Memo
|
||||||
|
datasource={
|
||||||
|
Object {
|
||||||
|
"languageProvider": Object {
|
||||||
|
"syntax": [Function],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
onChange={[MockFunction]}
|
onChange={[MockFunction]}
|
||||||
onKeyDownFunc={[Function]}
|
onKeyDownFunc={[Function]}
|
||||||
onQueryTypeChange={[Function]}
|
onQueryTypeChange={[Function]}
|
||||||
|
|||||||
+10
@@ -172,6 +172,7 @@ exports[`Render PromQueryEditor with basic options should render 1`] = `
|
|||||||
}
|
}
|
||||||
query={
|
query={
|
||||||
Object {
|
Object {
|
||||||
|
"exemplar": true,
|
||||||
"expr": "",
|
"expr": "",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "",
|
"legendFormat": "",
|
||||||
@@ -182,10 +183,19 @@ exports[`Render PromQueryEditor with basic options should render 1`] = `
|
|||||||
</FormLabel>
|
</FormLabel>
|
||||||
</div>
|
</div>
|
||||||
<PromExemplarField
|
<PromExemplarField
|
||||||
|
datasource={
|
||||||
|
Object {
|
||||||
|
"createQuery": [MockFunction],
|
||||||
|
"getPrometheusTime": [MockFunction],
|
||||||
|
}
|
||||||
|
}
|
||||||
onChange={[MockFunction]}
|
onChange={[MockFunction]}
|
||||||
query={
|
query={
|
||||||
Object {
|
Object {
|
||||||
|
"exemplar": true,
|
||||||
"expr": "",
|
"expr": "",
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
"refId": "A",
|
"refId": "A",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_sr
|
|||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import defaults from 'lodash/defaults';
|
import defaults from 'lodash/defaults';
|
||||||
import LRU from 'lru-cache';
|
import LRU from 'lru-cache';
|
||||||
import { forkJoin, merge, Observable, of, pipe, throwError } from 'rxjs';
|
import { forkJoin, merge, Observable, of, pipe, Subject, throwError } from 'rxjs';
|
||||||
import { catchError, filter, map, tap } from 'rxjs/operators';
|
import { catchError, filter, map, tap } from 'rxjs/operators';
|
||||||
import addLabelToQuery from './add_label_to_query';
|
import addLabelToQuery from './add_label_to_query';
|
||||||
import PrometheusLanguageProvider from './language_provider';
|
import PrometheusLanguageProvider from './language_provider';
|
||||||
@@ -62,6 +62,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
|||||||
exemplarTraceIdDestinations: ExemplarTraceIdDestination[] | undefined;
|
exemplarTraceIdDestinations: ExemplarTraceIdDestination[] | undefined;
|
||||||
lookupsDisabled: boolean;
|
lookupsDisabled: boolean;
|
||||||
customQueryParameters: any;
|
customQueryParameters: any;
|
||||||
|
exemplarErrors: Subject<FetchError> = new Subject();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
instanceSettings: DataSourceInstanceSettings<PromOptions>,
|
instanceSettings: DataSourceInstanceSettings<PromOptions>,
|
||||||
@@ -308,7 +309,16 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (query.exemplar) {
|
if (query.exemplar) {
|
||||||
return this.getExemplars(query).pipe(filterAndMapResponse);
|
return this.getExemplars(query).pipe(
|
||||||
|
catchError((err: FetchError) => {
|
||||||
|
this.exemplarErrors.next(err);
|
||||||
|
return of({
|
||||||
|
data: [],
|
||||||
|
state: LoadingState.Done,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
filterAndMapResponse
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.performTimeSeriesQuery(query, query.start, query.end).pipe(filterAndMapResponse);
|
return this.performTimeSeriesQuery(query, query.start, query.end).pipe(filterAndMapResponse);
|
||||||
@@ -346,7 +356,16 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (query.exemplar) {
|
if (query.exemplar) {
|
||||||
return this.getExemplars(query).pipe(filterAndMapResponse);
|
return this.getExemplars(query).pipe(
|
||||||
|
catchError((err: FetchError) => {
|
||||||
|
this.exemplarErrors.next(err);
|
||||||
|
return of({
|
||||||
|
data: [],
|
||||||
|
state: LoadingState.Done,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
filterAndMapResponse
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.performTimeSeriesQuery(query, query.start, query.end).pipe(filterAndMapResponse);
|
return this.performTimeSeriesQuery(query, query.start, query.end).pipe(filterAndMapResponse);
|
||||||
|
|||||||
Reference in New Issue
Block a user