mirror of https://github.com/grafana/grafana
parent
cf342f1933
commit
07d6b632b5
@ -1,144 +0,0 @@ |
||||
import { render, screen } from '@testing-library/react'; |
||||
import React from 'react'; |
||||
|
||||
import { LoadingState, PanelData, toUtc, TimeRange } from '@grafana/data'; |
||||
|
||||
import { PrometheusDatasource } from '../datasource'; |
||||
import { PromQuery } from '../types'; |
||||
|
||||
import { testIds as extraFieldTestIds } from './PromExploreExtraField'; |
||||
import { PromExploreQueryEditor, testIds } from './PromExploreQueryEditor'; |
||||
|
||||
// the monaco-based editor uses lazy-loading and that does not work
|
||||
// well with this test, and we do not need the monaco-related
|
||||
// functionality in this test anyway, so we mock it out.
|
||||
jest.mock('./monaco-query-field/MonacoQueryFieldWrapper', () => { |
||||
const fakeQueryField = () => <div>prometheus query field</div>; |
||||
return { |
||||
MonacoQueryFieldWrapper: fakeQueryField, |
||||
}; |
||||
}); |
||||
|
||||
const setup = (propOverrides?: object) => { |
||||
const datasourceMock: unknown = { |
||||
languageProvider: { |
||||
syntax: () => {}, |
||||
getLabelKeys: () => [], |
||||
metrics: [], |
||||
start: () => Promise.resolve([]), |
||||
}, |
||||
getInitHints: () => [], |
||||
exemplarsAvailable: true, |
||||
}; |
||||
const datasource: PrometheusDatasource = datasourceMock as PrometheusDatasource; |
||||
const onRunQuery = jest.fn(); |
||||
const onChange = jest.fn(); |
||||
const query: PromQuery = { expr: '', refId: 'A', interval: '1s', exemplar: true }; |
||||
const range: TimeRange = { |
||||
from: toUtc('2020-01-01', 'YYYY-MM-DD'), |
||||
to: toUtc('2020-01-02', 'YYYY-MM-DD'), |
||||
raw: { |
||||
from: toUtc('2020-01-01', 'YYYY-MM-DD'), |
||||
to: toUtc('2020-01-02', 'YYYY-MM-DD'), |
||||
}, |
||||
}; |
||||
const data: PanelData = { |
||||
state: LoadingState.NotStarted, |
||||
series: [], |
||||
request: { |
||||
requestId: '1', |
||||
dashboardId: 1, |
||||
intervalMs: 1000, |
||||
interval: '1s', |
||||
panelId: 1, |
||||
range: { |
||||
from: toUtc('2020-01-01', 'YYYY-MM-DD'), |
||||
to: toUtc('2020-01-02', 'YYYY-MM-DD'), |
||||
raw: { |
||||
from: toUtc('2020-01-01', 'YYYY-MM-DD'), |
||||
to: toUtc('2020-01-02', 'YYYY-MM-DD'), |
||||
}, |
||||
}, |
||||
scopedVars: {}, |
||||
targets: [], |
||||
timezone: 'GMT', |
||||
app: 'Grafana', |
||||
startTime: 0, |
||||
}, |
||||
timeRange: { |
||||
from: toUtc('2020-01-01', 'YYYY-MM-DD'), |
||||
to: toUtc('2020-01-02', 'YYYY-MM-DD'), |
||||
raw: { |
||||
from: toUtc('2020-01-01', 'YYYY-MM-DD'), |
||||
to: toUtc('2020-01-02', 'YYYY-MM-DD'), |
||||
}, |
||||
}, |
||||
}; |
||||
const history: any[] = []; |
||||
const exploreMode = 'Metrics'; |
||||
|
||||
const props: any = { |
||||
query, |
||||
data, |
||||
range, |
||||
datasource, |
||||
exploreMode, |
||||
history, |
||||
onChange, |
||||
onRunQuery, |
||||
}; |
||||
|
||||
Object.assign(props, propOverrides); |
||||
|
||||
return <PromExploreQueryEditor {...props} />; |
||||
}; |
||||
|
||||
describe('PromExploreQueryEditor', () => { |
||||
it('should render component', () => { |
||||
render(setup()); |
||||
expect(screen.getByTestId(testIds.editor)).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should render PromQueryField with ExtraFieldElement', async () => { |
||||
render(setup()); |
||||
expect(screen.getByTestId(extraFieldTestIds.extraFieldEditor)).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should set default value for expr if it is undefined', async () => { |
||||
const onChange = jest.fn(); |
||||
const query = { expr: undefined, exemplar: false, instant: false, range: true }; |
||||
render(setup({ onChange, query })); |
||||
expect(onChange).toHaveBeenCalledTimes(1); |
||||
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ expr: '' })); |
||||
}); |
||||
|
||||
it('should set default value for exemplars if it is undefined', async () => { |
||||
const onChange = jest.fn(); |
||||
const query = { expr: '', instant: false, range: true }; |
||||
render(setup({ onChange, query })); |
||||
expect(onChange).toHaveBeenCalledTimes(1); |
||||
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ exemplar: true })); |
||||
}); |
||||
|
||||
it('should set default value for instant and range if expr is falsy', async () => { |
||||
const onChange = jest.fn(); |
||||
let query = { expr: '', exemplar: true }; |
||||
render(setup({ onChange, query })); |
||||
expect(onChange).toHaveBeenCalledTimes(1); |
||||
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ instant: true, range: true })); |
||||
}); |
||||
|
||||
it('should not set default value for instant and range with truthy expr', async () => { |
||||
const onChange = jest.fn(); |
||||
let query = { expr: 'foo', exemplar: true }; |
||||
render(setup({ onChange, query })); |
||||
expect(onChange).toHaveBeenCalledTimes(0); |
||||
}); |
||||
|
||||
it('should add default values for multiple missing values', async () => { |
||||
const onChange = jest.fn(); |
||||
let query = {}; |
||||
render(setup({ onChange, query })); |
||||
expect(onChange).toHaveBeenCalledTimes(3); |
||||
}); |
||||
}); |
@ -1,54 +0,0 @@ |
||||
import React, { memo, useEffect } from 'react'; |
||||
|
||||
import { QueryEditorProps, CoreApp } from '@grafana/data'; |
||||
|
||||
import { PrometheusDatasource } from '../datasource'; |
||||
import { PromQuery, PromOptions } from '../types'; |
||||
|
||||
import { PromExploreExtraField } from './PromExploreExtraField'; |
||||
import PromQueryField from './PromQueryField'; |
||||
|
||||
export type Props = QueryEditorProps<PrometheusDatasource, PromQuery, PromOptions>; |
||||
|
||||
export const PromExploreQueryEditor = memo((props: Props) => { |
||||
const { range, query, data, datasource, history, onChange, onRunQuery } = props; |
||||
|
||||
// Setting default values
|
||||
useEffect(() => { |
||||
if (query.expr === undefined) { |
||||
onChange({ ...query, expr: '' }); |
||||
} |
||||
if (query.exemplar === undefined) { |
||||
onChange({ ...query, exemplar: true }); |
||||
} |
||||
|
||||
// Override query type to "Both" only for new queries (no query.expr).
|
||||
if (!query.instant && !query.range && !query.expr) { |
||||
onChange({ ...query, instant: true, range: true }); |
||||
} |
||||
}, [onChange, query]); |
||||
|
||||
return ( |
||||
<PromQueryField |
||||
app={CoreApp.Explore} |
||||
datasource={datasource} |
||||
query={query} |
||||
range={range} |
||||
onRunQuery={onRunQuery} |
||||
onChange={onChange} |
||||
onBlur={() => {}} |
||||
history={history} |
||||
data={data} |
||||
data-testid={testIds.editor} |
||||
ExtraFieldElement={ |
||||
<PromExploreExtraField query={query} onChange={onChange} datasource={datasource} onRunQuery={onRunQuery} /> |
||||
} |
||||
/> |
||||
); |
||||
}); |
||||
|
||||
PromExploreQueryEditor.displayName = 'PromExploreQueryEditor'; |
||||
|
||||
export const testIds = { |
||||
editor: 'prom-editor-explore', |
||||
}; |
@ -1,75 +0,0 @@ |
||||
import { screen, render } from '@testing-library/react'; |
||||
import React from 'react'; |
||||
|
||||
import { dateTime, CoreApp } from '@grafana/data'; |
||||
|
||||
import { PrometheusDatasource } from '../datasource'; |
||||
import { PromQuery } from '../types'; |
||||
|
||||
import { PromQueryEditor, testIds } from './PromQueryEditor'; |
||||
|
||||
jest.mock('app/features/dashboard/services/TimeSrv', () => { |
||||
return { |
||||
getTimeSrv: () => ({ |
||||
timeRange: () => ({ |
||||
from: dateTime(), |
||||
to: dateTime(), |
||||
}), |
||||
}), |
||||
}; |
||||
}); |
||||
|
||||
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(); |
||||
const onChange = jest.fn(); |
||||
const query: PromQuery = { expr: '', refId: 'A' }; |
||||
|
||||
const props: any = { |
||||
datasource, |
||||
onChange, |
||||
onRunQuery, |
||||
query, |
||||
}; |
||||
|
||||
Object.assign(props, propOverrides); |
||||
|
||||
return render(<PromQueryEditor {...props} />); |
||||
}; |
||||
|
||||
describe('Render PromQueryEditor with basic options', () => { |
||||
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(); |
||||
}); |
||||
}); |
@ -1,223 +0,0 @@ |
||||
import { map } from 'lodash'; |
||||
import React, { PureComponent } from 'react'; |
||||
|
||||
// Types
|
||||
import { CoreApp, SelectableValue } from '@grafana/data'; |
||||
import { InlineFormLabel, LegacyForms, Select } from '@grafana/ui'; |
||||
|
||||
import { PromQuery } from '../types'; |
||||
|
||||
import { PromExemplarField } from './PromExemplarField'; |
||||
import PromLink from './PromLink'; |
||||
import PromQueryField from './PromQueryField'; |
||||
import { PromQueryEditorProps } from './types'; |
||||
|
||||
const { Switch } = LegacyForms; |
||||
|
||||
export const FORMAT_OPTIONS: Array<SelectableValue<string>> = [ |
||||
{ label: 'Time series', value: 'time_series' }, |
||||
{ label: 'Table', value: 'table' }, |
||||
{ label: 'Heatmap', value: 'heatmap' }, |
||||
]; |
||||
|
||||
export const INTERVAL_FACTOR_OPTIONS: Array<SelectableValue<number>> = map([1, 2, 3, 4, 5, 10], (value: number) => ({ |
||||
value, |
||||
label: '1/' + value, |
||||
})); |
||||
|
||||
interface State { |
||||
legendFormat?: string; |
||||
formatOption: SelectableValue<string>; |
||||
interval?: string; |
||||
intervalFactorOption: SelectableValue<number>; |
||||
instant: boolean; |
||||
exemplar: boolean; |
||||
} |
||||
|
||||
export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State> { |
||||
// Query target to be modified and used for queries
|
||||
query: PromQuery; |
||||
|
||||
constructor(props: PromQueryEditorProps) { |
||||
super(props); |
||||
// Use default query to prevent undefined input values
|
||||
const defaultQuery: Partial<PromQuery> = { |
||||
expr: '', |
||||
legendFormat: '', |
||||
interval: '', |
||||
// Set exemplar to false for alerting queries
|
||||
exemplar: props.app === CoreApp.UnifiedAlerting ? false : true, |
||||
}; |
||||
const query = Object.assign({}, defaultQuery, props.query); |
||||
this.query = query; |
||||
// Query target properties that are fully controlled inputs
|
||||
this.state = { |
||||
// Fully controlled text inputs
|
||||
interval: query.interval, |
||||
legendFormat: query.legendFormat, |
||||
// Select options
|
||||
formatOption: FORMAT_OPTIONS.find((option) => option.value === query.format) || FORMAT_OPTIONS[0], |
||||
intervalFactorOption: |
||||
INTERVAL_FACTOR_OPTIONS.find((option) => option.value === query.intervalFactor) || INTERVAL_FACTOR_OPTIONS[0], |
||||
// Switch options
|
||||
instant: Boolean(query.instant), |
||||
exemplar: Boolean(query.exemplar), |
||||
}; |
||||
} |
||||
|
||||
onFieldChange = (query: PromQuery, override?: any) => { |
||||
this.query.expr = query.expr; |
||||
}; |
||||
|
||||
onFormatChange = (option: SelectableValue<string>) => { |
||||
this.query.format = option.value; |
||||
this.setState({ formatOption: option }, this.onRunQuery); |
||||
}; |
||||
|
||||
onInstantChange = (e: React.SyntheticEvent<HTMLInputElement>) => { |
||||
const instant = e.currentTarget.checked; |
||||
this.query.instant = instant; |
||||
this.setState({ instant }, this.onRunQuery); |
||||
}; |
||||
|
||||
onIntervalChange = (e: React.SyntheticEvent<HTMLInputElement>) => { |
||||
const interval = e.currentTarget.value; |
||||
this.query.interval = interval; |
||||
this.setState({ interval }); |
||||
}; |
||||
|
||||
onIntervalFactorChange = (option: SelectableValue<number>) => { |
||||
this.query.intervalFactor = option.value; |
||||
this.setState({ intervalFactorOption: option }, this.onRunQuery); |
||||
}; |
||||
|
||||
onLegendChange = (e: React.SyntheticEvent<HTMLInputElement>) => { |
||||
const legendFormat = e.currentTarget.value; |
||||
this.query.legendFormat = legendFormat; |
||||
this.setState({ legendFormat }); |
||||
}; |
||||
|
||||
onExemplarChange = (isEnabled: boolean) => { |
||||
this.query.exemplar = isEnabled; |
||||
this.setState({ exemplar: isEnabled }, this.onRunQuery); |
||||
}; |
||||
|
||||
onRunQuery = () => { |
||||
const { query } = this; |
||||
// Change of query.hide happens outside of this component and is just passed as prop. We have to update it when running queries.
|
||||
const { hide } = this.props.query; |
||||
this.props.onChange({ ...query, hide }); |
||||
this.props.onRunQuery(); |
||||
}; |
||||
|
||||
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 |
||||
datasource={datasource} |
||||
query={query} |
||||
range={range} |
||||
onRunQuery={this.onRunQuery} |
||||
onChange={this.onFieldChange} |
||||
history={[]} |
||||
data={data} |
||||
data-testid={testIds.editor} |
||||
ExtraFieldElement={ |
||||
<div className="gf-form-inline"> |
||||
<div className="gf-form"> |
||||
<InlineFormLabel |
||||
width={7} |
||||
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." |
||||
> |
||||
Legend |
||||
</InlineFormLabel> |
||||
<input |
||||
type="text" |
||||
className="gf-form-input" |
||||
placeholder="legend format" |
||||
value={legendFormat} |
||||
onChange={this.onLegendChange} |
||||
onBlur={this.onRunQuery} |
||||
/> |
||||
</div> |
||||
|
||||
<div className="gf-form"> |
||||
<InlineFormLabel |
||||
width={7} |
||||
tooltip={ |
||||
<> |
||||
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. |
||||
</> |
||||
} |
||||
> |
||||
Min step |
||||
</InlineFormLabel> |
||||
<input |
||||
type="text" |
||||
className="gf-form-input width-8" |
||||
aria-label="Set lower limit for the step parameter" |
||||
placeholder={interval} |
||||
onChange={this.onIntervalChange} |
||||
onBlur={this.onRunQuery} |
||||
value={interval} |
||||
/> |
||||
</div> |
||||
|
||||
<div className="gf-form"> |
||||
<div className="gf-form-label">Resolution</div> |
||||
<Select |
||||
aria-label="Select resolution" |
||||
isSearchable={false} |
||||
options={INTERVAL_FACTOR_OPTIONS} |
||||
onChange={this.onIntervalFactorChange} |
||||
value={intervalFactorOption} |
||||
/> |
||||
</div> |
||||
|
||||
<div className="gf-form"> |
||||
<div className="gf-form-label width-7">Format</div> |
||||
<Select |
||||
className="select-container" |
||||
width={16} |
||||
isSearchable={false} |
||||
options={FORMAT_OPTIONS} |
||||
onChange={this.onFormatChange} |
||||
value={formatOption} |
||||
aria-label="Select format" |
||||
/> |
||||
<Switch label="Instant" checked={instant} onChange={this.onInstantChange} /> |
||||
|
||||
<InlineFormLabel width={10} tooltip="Link to Graph in Prometheus"> |
||||
<PromLink |
||||
datasource={datasource} |
||||
query={this.query} // Use modified query
|
||||
panelData={data} |
||||
/> |
||||
</InlineFormLabel> |
||||
</div> |
||||
{showExemplarField && ( |
||||
<PromExemplarField |
||||
onChange={this.onExemplarChange} |
||||
datasource={datasource} |
||||
query={this.query} |
||||
data-testid={testIds.exemplar} |
||||
/> |
||||
)} |
||||
</div> |
||||
} |
||||
/> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export const testIds = { |
||||
editor: 'prom-editor', |
||||
exemplar: 'exemplar-editor', |
||||
}; |
Loading…
Reference in new issue