Datasource: updates PromExploreQueryEditor to prevent it from throwing error on edit (#21605)

* Datasource: updates PromExploreQueryEditor - rewrite to functional component

* Datasource: updates PromQueryField - moves an extra field from children to the separate prop

* Datasource: adds PromExploreExtraField

* Datasource: updates PromExploreQueryEditor - fixes typo

* Datasource: updates prometheus explore editor snapshots

* Datasource: updates PromExploreExtraField export

* Datasource: removes unnecessary div from PromExploreQueryEditor

* Datasource: adds basic PromExploreExtraField snapshot test

* Datasource: adds basic PromExploreQueryEditor test

* Datasource: updates PromExploreQueryEditor snapshot to fix timezone issues

* Datasource: updates PromExploreQueryEditor - onChangeQueryStep cleanup

* Datasource: updates PromExploreQueryEditor test to check ExtraFieldElement render

* Datasource: simplified PromExploreQueryEditor onStepChange method

* Datasource: updates Prometheus module import

* Datasource: updates PromExploreQueryEditor test

* Datasource: updates PromExploreQueryEditor tests

* Datasource: fixes PromExploreQueryEditor error on empty interval init

* Datasource: adds a tooltip to PromExploreExtraField mounted in PromExploreQueryEditor

* Datasource: updates PromExploreQueryEditor snapshots
This commit is contained in:
Lukas Siatka
2020-02-06 12:37:30 +00:00
committed by GitHub
parent df48d1c19f
commit 2d3c5064e1
8 changed files with 285 additions and 73 deletions

View File

@@ -0,0 +1,28 @@
import React from 'react';
import { shallow } from 'enzyme';
import { PromExploreExtraField, PromExploreExtraFieldProps } from './PromExploreExtraField';
const setup = (propOverrides?: PromExploreExtraFieldProps) => {
const label = 'Prometheus Explore Extra Field';
const value = '123';
const onChangeFunc = jest.fn();
const onKeyDownFunc = jest.fn();
const props: any = {
label,
value,
onChangeFunc,
onKeyDownFunc,
};
Object.assign(props, propOverrides);
return shallow(<PromExploreExtraField {...props} />);
};
describe('PrometheusExploreExtraField', () => {
it('should render component', () => {
const wrapper = setup();
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,38 @@
// Libraries
import React, { memo } from 'react';
// Types
import { FormLabel } from '@grafana/ui';
export interface PromExploreExtraFieldProps {
label: string;
onChangeFunc: (e: React.SyntheticEvent<HTMLInputElement>) => void;
onKeyDownFunc: (e: React.KeyboardEvent<HTMLInputElement>) => void;
value: string;
hasTooltip?: boolean;
tooltipContent?: string;
}
export function PromExploreExtraField(props: PromExploreExtraFieldProps) {
const { label, onChangeFunc, onKeyDownFunc, value, hasTooltip, tooltipContent } = props;
return (
<div className="gf-form-inline explore-input--ml">
<div className="gf-form">
<FormLabel width={5} tooltip={hasTooltip ? tooltipContent : null}>
{label}
</FormLabel>
<input
type={'text'}
className="gf-form-input width-4"
placeholder={'auto'}
onChange={onChangeFunc}
onKeyDown={onKeyDownFunc}
value={value}
/>
</div>
</div>
);
}
export default memo(PromExploreExtraField);

View File

@@ -0,0 +1,87 @@
import React from 'react';
import { shallow, mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import PromExploreQueryEditor from './PromExploreQueryEditor';
import { PromExploreExtraField } from './PromExploreExtraField';
import { PrometheusDatasource } from '../datasource';
import { PromQuery } from '../types';
import { PanelData, LoadingState, dateTime } from '@grafana/data';
const setup = (renderMethod: any, propOverrides?: object) => {
const datasourceMock: unknown = {};
const datasource: PrometheusDatasource = datasourceMock as PrometheusDatasource;
const onRunQuery = jest.fn();
const onChange = jest.fn();
const query: PromQuery = { expr: '', refId: 'A', interval: '1s' };
const data: PanelData = {
state: LoadingState.NotStarted,
series: [],
request: {
requestId: '1',
dashboardId: 1,
interval: '1s',
panelId: 1,
range: {
from: dateTime('2020-01-01', 'YYYY-MM-DD'),
to: dateTime('2020-01-02', 'YYYY-MM-DD'),
raw: {
from: dateTime('2020-01-01', 'YYYY-MM-DD'),
to: dateTime('2020-01-02', 'YYYY-MM-DD'),
},
},
scopedVars: {},
targets: [],
timezone: 'GMT',
app: 'Grafana',
startTime: 0,
},
timeRange: {
from: dateTime('2020-01-01', 'YYYY-MM-DD'),
to: dateTime('2020-01-02', 'YYYY-MM-DD'),
raw: {
from: dateTime('2020-01-01', 'YYYY-MM-DD'),
to: dateTime('2020-01-02', 'YYYY-MM-DD'),
},
},
};
const history: any[] = [];
const exploreMode = 'Metrics';
const props: any = {
query,
data,
datasource,
exploreMode,
history,
onChange,
onRunQuery,
};
Object.assign(props, propOverrides);
return renderMethod(<PromExploreQueryEditor {...props} />);
};
describe('PromExploreQueryEditor', () => {
let originalGetSelection: typeof window.getSelection;
beforeAll(() => {
originalGetSelection = window.getSelection;
window.getSelection = () => null;
});
afterAll(() => {
window.getSelection = originalGetSelection;
});
it('should render component', () => {
const wrapper = setup(shallow);
expect(wrapper).toMatchSnapshot();
});
it('should render PromQueryField with ExtraFieldElement', async () => {
await act(async () => {
const wrapper = setup(mount);
expect(wrapper.find(PromExploreExtraField).length).toBe(1);
});
});
});

View File

@@ -1,85 +1,57 @@
import React, { PureComponent } from 'react';
import React, { memo } from 'react';
// Types
import { ExploreQueryFieldProps } from '@grafana/data';
import { FormLabel } from '@grafana/ui';
import { PrometheusDatasource } from '../datasource';
import { PromQuery, PromOptions } from '../types';
import PromQueryField from './PromQueryField';
import { PromExploreExtraField } from './PromExploreExtraField';
export type Props = ExploreQueryFieldProps<PrometheusDatasource, PromQuery, PromOptions>;
interface State {
interval: string;
}
export function PromExploreQueryEditor(props: Props) {
const { query, data, datasource, history, onChange, onRunQuery } = props;
export class PromExploreQueryEditor extends PureComponent<Props, State> {
// Query target to be modified and used for queries
query: PromQuery;
constructor(props: Props) {
super(props);
const { query } = props;
this.query = query;
// Query target properties that are fully controlled inputs
this.state = {
// Fully controlled text inputs
interval: query.interval,
};
function onChangeQueryStep(value: string) {
const { query, onChange } = props;
const nextQuery = { ...query, interval: value };
onChange(nextQuery);
}
onFieldChange = (query: PromQuery, override?: any) => {
this.query.expr = query.expr;
};
function onStepChange(e: React.SyntheticEvent<HTMLInputElement>) {
if (e.currentTarget.value !== query.interval) {
onChangeQueryStep(e.currentTarget.value);
}
}
onIntervalChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
const interval = e.currentTarget.value;
this.query.interval = interval;
this.setState({ interval });
};
onRunQuery = () => {
const { query } = this;
this.props.onChange(query);
this.props.onRunQuery();
};
onReturnKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
function onReturnKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === 'Enter') {
this.onRunQuery();
onRunQuery();
}
}
};
render() {
const { datasource, query, data, history } = this.props;
const { interval } = this.state;
return (
<div className="gf-form-inline">
<PromQueryField
datasource={datasource}
query={query}
onRunQuery={this.onRunQuery}
onChange={this.onFieldChange}
onRunQuery={onRunQuery}
onChange={onChange}
history={history}
data={data}
>
<div className="gf-form-inline explore-input--ml">
<div className="gf-form">
<FormLabel width={4}>Step</FormLabel>
<input
type="text"
className="gf-form-input width-6"
placeholder={'auto'}
onChange={this.onIntervalChange}
onKeyDown={this.onReturnKeyDown}
value={interval}
ExtraFieldElement={
<PromExploreExtraField
label={'Step'}
onChangeFunc={onStepChange}
onKeyDownFunc={onReturnKeyDown}
value={query.interval || ''}
hasTooltip={true}
tooltipContent={'Needs to be a valid time unit string, for example 5s, 1m, 3h, 1d, 1y'}
/>
</div>
</div>
</PromQueryField>
</div>
);
}
/>
);
}
export default memo(PromExploreQueryEditor);

View File

@@ -1,5 +1,5 @@
import _ from 'lodash';
import React from 'react';
import React, { ReactNode } from 'react';
import { Plugin } from 'slate';
import {
@@ -110,6 +110,7 @@ export function willApplySuggestion(suggestion: string, { typeaheadContext, type
interface PromQueryFieldProps extends ExploreQueryFieldProps<PrometheusDatasource, PromQuery, PromOptions> {
history: Array<HistoryItem<PromQuery>>;
ExtraFieldElement?: ReactNode;
}
interface PromQueryFieldState {
@@ -291,7 +292,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
};
render() {
const { data, query, children } = this.props;
const { data, query, ExtraFieldElement } = this.props;
const { metricsOptions, syntaxLoaded, hint } = this.state;
const cleanText = this.languageProvider ? this.languageProvider.cleanText : undefined;
const chooserText = getChooserText(syntaxLoaded, metricsOptions);
@@ -321,7 +322,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
syntaxLoaded={syntaxLoaded}
/>
</div>
{children}
{ExtraFieldElement}
</div>
{showError ? (
<div className="query-row-break">

View File

@@ -0,0 +1,26 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PrometheusExploreExtraField should render component 1`] = `
<div
className="gf-form-inline explore-input--ml"
>
<div
className="gf-form"
>
<Component
tooltip={null}
width={5}
>
Prometheus Explore Extra Field
</Component>
<input
className="gf-form-input width-4"
onChange={[MockFunction]}
onKeyDown={[MockFunction]}
placeholder="auto"
type="text"
value="123"
/>
</div>
</div>
`;

View File

@@ -0,0 +1,60 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PromExploreQueryEditor should render component 1`] = `
<PromQueryField
ExtraFieldElement={
<PromExploreExtraField
hasTooltip={true}
label="Step"
onChangeFunc={[Function]}
onKeyDownFunc={[Function]}
tooltipContent="Needs to be a valid time unit string, for example 5s, 1m, 3h, 1d, 1y"
value="1s"
/>
}
data={
Object {
"request": Object {
"app": "Grafana",
"dashboardId": 1,
"interval": "1s",
"panelId": 1,
"range": Object {
"from": "2020-01-01T00:00:00.000Z",
"raw": Object {
"from": "2020-01-01T00:00:00.000Z",
"to": "2020-01-02T00:00:00.000Z",
},
"to": "2020-01-02T00:00:00.000Z",
},
"requestId": "1",
"scopedVars": Object {},
"startTime": 0,
"targets": Array [],
"timezone": "GMT",
},
"series": Array [],
"state": "NotStarted",
"timeRange": Object {
"from": "2020-01-01T00:00:00.000Z",
"raw": Object {
"from": "2020-01-01T00:00:00.000Z",
"to": "2020-01-02T00:00:00.000Z",
},
"to": "2020-01-02T00:00:00.000Z",
},
}
}
datasource={Object {}}
history={Array []}
onChange={[MockFunction]}
onRunQuery={[MockFunction]}
query={
Object {
"expr": "",
"interval": "1s",
"refId": "A",
}
}
/>
`;

View File

@@ -3,7 +3,7 @@ import { ANNOTATION_QUERY_STEP_DEFAULT, PrometheusDatasource } from './datasourc
import { PromQueryEditor } from './components/PromQueryEditor';
import PromCheatSheet from './components/PromCheatSheet';
import { PromExploreQueryEditor } from './components/PromExploreQueryEditor';
import PromExploreQueryEditor from './components/PromExploreQueryEditor';
import { ConfigEditor } from './configuration/ConfigEditor';