[Lens] Show validation feedback on top values out of bounds number of values (#110222)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
c568a433f3
commit
3b81205a23
|
@ -348,13 +348,6 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EuiFormRow
|
|
||||||
label={i18n.translate('xpack.lens.indexPattern.terms.size', {
|
|
||||||
defaultMessage: 'Number of values',
|
|
||||||
})}
|
|
||||||
display="columnCompressed"
|
|
||||||
fullWidth
|
|
||||||
>
|
|
||||||
<ValuesInput
|
<ValuesInput
|
||||||
value={currentColumn.params.size}
|
value={currentColumn.params.size}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
@ -368,7 +361,6 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</EuiFormRow>
|
|
||||||
<EuiFormRow
|
<EuiFormRow
|
||||||
label={
|
label={
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import { EuiFieldNumber } from '@elastic/eui';
|
import { EuiFieldNumber, EuiFormRow } from '@elastic/eui';
|
||||||
import { ValuesInput } from './values_input';
|
import { ValuesInput } from './values_input';
|
||||||
|
|
||||||
jest.mock('react-use/lib/useDebounce', () => (fn: () => void) => fn());
|
jest.mock('react-use/lib/useDebounce', () => (fn: () => void) => fn());
|
||||||
|
@ -41,7 +41,7 @@ describe('Values', () => {
|
||||||
expect(onChangeSpy.mock.calls[0][0]).toBe(7);
|
expect(onChangeSpy.mock.calls[0][0]).toBe(7);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not run onChange function on update when value is out of 1-100 range', () => {
|
it('should not run onChange function on update when value is out of 1-1000 range', () => {
|
||||||
const onChangeSpy = jest.fn();
|
const onChangeSpy = jest.fn();
|
||||||
const instance = shallow(<ValuesInput value={5} onChange={onChangeSpy} />);
|
const instance = shallow(<ValuesInput value={5} onChange={onChangeSpy} />);
|
||||||
act(() => {
|
act(() => {
|
||||||
|
@ -54,4 +54,56 @@ describe('Values', () => {
|
||||||
expect(onChangeSpy.mock.calls.length).toBe(1);
|
expect(onChangeSpy.mock.calls.length).toBe(1);
|
||||||
expect(onChangeSpy.mock.calls[0][0]).toBe(1000);
|
expect(onChangeSpy.mock.calls[0][0]).toBe(1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show an error message when the value is out of bounds', () => {
|
||||||
|
const instance = shallow(<ValuesInput value={-5} onChange={jest.fn()} />);
|
||||||
|
|
||||||
|
expect(instance.find(EuiFieldNumber).prop('isInvalid')).toBeTruthy();
|
||||||
|
expect(instance.find(EuiFormRow).prop('error')).toEqual(
|
||||||
|
expect.arrayContaining([expect.stringMatching('Value is lower')])
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
instance.find(EuiFieldNumber).prop('onChange')!({
|
||||||
|
currentTarget: { value: '1007' },
|
||||||
|
} as React.ChangeEvent<HTMLInputElement>);
|
||||||
|
});
|
||||||
|
instance.update();
|
||||||
|
|
||||||
|
expect(instance.find(EuiFieldNumber).prop('isInvalid')).toBeTruthy();
|
||||||
|
expect(instance.find(EuiFormRow).prop('error')).toEqual(
|
||||||
|
expect.arrayContaining([expect.stringMatching('Value is higher')])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fallback to last valid value on input blur', () => {
|
||||||
|
const instance = shallow(<ValuesInput value={123} onChange={jest.fn()} />);
|
||||||
|
|
||||||
|
function changeAndBlur(newValue: string) {
|
||||||
|
act(() => {
|
||||||
|
instance.find(EuiFieldNumber).prop('onChange')!({
|
||||||
|
currentTarget: { value: newValue },
|
||||||
|
} as React.ChangeEvent<HTMLInputElement>);
|
||||||
|
});
|
||||||
|
instance.update();
|
||||||
|
act(() => {
|
||||||
|
instance.find(EuiFieldNumber).prop('onBlur')!({} as React.FocusEvent<HTMLInputElement>);
|
||||||
|
});
|
||||||
|
instance.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
changeAndBlur('-5');
|
||||||
|
|
||||||
|
expect(instance.find(EuiFieldNumber).prop('isInvalid')).toBeFalsy();
|
||||||
|
expect(instance.find(EuiFieldNumber).prop('value')).toBe('1');
|
||||||
|
|
||||||
|
changeAndBlur('5000');
|
||||||
|
|
||||||
|
expect(instance.find(EuiFieldNumber).prop('isInvalid')).toBeFalsy();
|
||||||
|
expect(instance.find(EuiFieldNumber).prop('value')).toBe('1000');
|
||||||
|
|
||||||
|
changeAndBlur('');
|
||||||
|
// as we're not handling the onChange state, it fallbacks to the value prop
|
||||||
|
expect(instance.find(EuiFieldNumber).prop('value')).toBe('123');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { EuiFieldNumber } from '@elastic/eui';
|
import { EuiFieldNumber, EuiFormRow } from '@elastic/eui';
|
||||||
import { useDebounceWithOptions } from '../../../../shared_components';
|
import { useDebounceWithOptions } from '../../../../shared_components';
|
||||||
|
|
||||||
export const ValuesInput = ({
|
export const ValuesInput = ({
|
||||||
|
@ -35,17 +35,63 @@ export const ValuesInput = ({
|
||||||
[inputValue]
|
[inputValue]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isEmptyString = inputValue === '';
|
||||||
|
const isHigherThanMax = !isEmptyString && Number(inputValue) > MAX_NUMBER_OF_VALUES;
|
||||||
|
const isLowerThanMin = !isEmptyString && Number(inputValue) < MIN_NUMBER_OF_VALUES;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<EuiFormRow
|
||||||
|
label={i18n.translate('xpack.lens.indexPattern.terms.size', {
|
||||||
|
defaultMessage: 'Number of values',
|
||||||
|
})}
|
||||||
|
display="columnCompressed"
|
||||||
|
fullWidth
|
||||||
|
isInvalid={isHigherThanMax || isLowerThanMin}
|
||||||
|
error={
|
||||||
|
isHigherThanMax
|
||||||
|
? [
|
||||||
|
i18n.translate('xpack.lens.indexPattern.terms.sizeLimitMax', {
|
||||||
|
defaultMessage:
|
||||||
|
'Value is higher than the maximum {max}, the maximum value is used instead.',
|
||||||
|
values: {
|
||||||
|
max: MAX_NUMBER_OF_VALUES,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
: isLowerThanMin
|
||||||
|
? [
|
||||||
|
i18n.translate('xpack.lens.indexPattern.terms.sizeLimitMin', {
|
||||||
|
defaultMessage:
|
||||||
|
'Value is lower than the minimum {min}, the minimum value is used instead.',
|
||||||
|
values: {
|
||||||
|
min: MIN_NUMBER_OF_VALUES,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
>
|
||||||
<EuiFieldNumber
|
<EuiFieldNumber
|
||||||
min={MIN_NUMBER_OF_VALUES}
|
min={MIN_NUMBER_OF_VALUES}
|
||||||
max={MAX_NUMBER_OF_VALUES}
|
max={MAX_NUMBER_OF_VALUES}
|
||||||
step={1}
|
step={1}
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
compressed
|
compressed
|
||||||
|
isInvalid={isHigherThanMax || isLowerThanMin}
|
||||||
onChange={({ currentTarget }) => setInputValue(currentTarget.value)}
|
onChange={({ currentTarget }) => setInputValue(currentTarget.value)}
|
||||||
aria-label={i18n.translate('xpack.lens.indexPattern.terms.size', {
|
aria-label={i18n.translate('xpack.lens.indexPattern.terms.size', {
|
||||||
defaultMessage: 'Number of values',
|
defaultMessage: 'Number of values',
|
||||||
})}
|
})}
|
||||||
|
onBlur={() => {
|
||||||
|
if (inputValue === '') {
|
||||||
|
return setInputValue(String(value));
|
||||||
|
}
|
||||||
|
const inputNumber = Number(inputValue);
|
||||||
|
setInputValue(
|
||||||
|
String(Math.min(MAX_NUMBER_OF_VALUES, Math.max(inputNumber, MIN_NUMBER_OF_VALUES)))
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
</EuiFormRow>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue