diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx index a8851c79be2ae..f5e21c5d8aeeb 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { ChangeEvent } from 'react'; import { shallow, mount } from 'enzyme'; import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'kibana/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; @@ -14,11 +14,20 @@ import { createMockedIndexPattern } from '../../mocks'; import { percentileOperation } from './index'; import { IndexPattern, IndexPatternLayer } from '../../types'; import { PercentileIndexPatternColumn } from './percentile'; -import { EuiFieldNumber } from '@elastic/eui'; +import { EuiRange } from '@elastic/eui'; import { act } from 'react-dom/test-utils'; import { EuiFormRow } from '@elastic/eui'; import { TermsIndexPatternColumn } from './terms'; +jest.mock('lodash', () => { + const original = jest.requireActual('lodash'); + + return { + ...original, + debounce: (fn: unknown) => fn, + }; +}); + const uiSettingsMock = {} as IUiSettingsClient; const defaultProps = { @@ -272,8 +281,7 @@ describe('percentile', () => { expect(input.prop('value')).toEqual('23'); }); - it('should update state on change', async () => { - jest.useFakeTimers(); + it('should update state on change', () => { const updateLayerSpy = jest.fn(); const instance = mount( { /> ); - jest.runAllTimers(); - - const input = instance.find( - '[data-test-subj="lns-indexPattern-percentile-input"] input[type="number"]' - ); + const input = instance + .find('[data-test-subj="lns-indexPattern-percentile-input"]') + .find(EuiRange); - await act(async () => { - input.simulate('change', { target: { value: '27' } }); + act(() => { + input.prop('onChange')!( + { currentTarget: { value: '27' } } as ChangeEvent, + true + ); }); instance.update(); - jest.runAllTimers(); - expect(updateLayerSpy).toHaveBeenCalledWith({ ...layer, columns: { @@ -314,7 +321,7 @@ describe('percentile', () => { }); }); - it('should not update on invalid input, but show invalid value locally', async () => { + it('should not update on invalid input, but show invalid value locally', () => { const updateLayerSpy = jest.fn(); const instance = mount( { /> ); - jest.runAllTimers(); - - const input = instance.find( - '[data-test-subj="lns-indexPattern-percentile-input"] input[type="number"]' - ); + const input = instance + .find('[data-test-subj="lns-indexPattern-percentile-input"]') + .find(EuiRange); - await act(async () => { - input.simulate('change', { target: { value: '12.12' } }); + act(() => { + input.prop('onChange')!( + { currentTarget: { value: '12.12' } } as ChangeEvent, + true + ); }); instance.update(); - jest.runAllTimers(); - expect(updateLayerSpy).not.toHaveBeenCalled(); expect( @@ -351,7 +357,7 @@ describe('percentile', () => { expect( instance .find('[data-test-subj="lns-indexPattern-percentile-input"]') - .find(EuiFieldNumber) + .find(EuiRange) .prop('value') ).toEqual('12.12'); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx index 9e6232f4fe264..d1ce42696ea68 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { EuiFieldNumber, EuiFormRow } from '@elastic/eui'; -import React, { useCallback, useState } from 'react'; +import { EuiFormRow, EuiRange, EuiRangeProps } from '@elastic/eui'; +import React, { useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { AggFunctionsMapping } from 'src/plugins/data/public'; import { buildExpressionFunction } from '../../../../../../../src/plugins/expressions/public'; @@ -21,7 +21,7 @@ import { } from './helpers'; import { FieldBasedIndexPatternColumn } from './column_types'; import { adjustTimeScaleLabelSuffix } from '../time_scale_utils'; -import { useDebounceWithOptions } from '../../../shared_components'; +import { useDebouncedValue } from '../../../shared_components'; export interface PercentileIndexPatternColumn extends FieldBasedIndexPatternColumn { operationType: 'percentile'; @@ -150,16 +150,14 @@ export const percentileOperation: OperationDefinition< columnId, indexPattern, }) { - const [inputValue, setInputValue] = useState(String(currentColumn.params.percentile)); - - const inputValueAsNumber = Number(inputValue); - // an input is value if it's not an empty string, parses to a valid number, is between 0 and 100 (exclusive) - // and is an integer - const inputValueIsValid = isValidNumber(inputValue, true, 99, 1); - - useDebounceWithOptions( - () => { - if (!inputValueIsValid) return; + const onChange = useCallback( + (value) => { + if ( + !isValidNumber(value, true, 99, 1) || + Number(value) === currentColumn.params.percentile + ) { + return; + } updateLayer({ ...layer, columns: { @@ -171,26 +169,32 @@ export const percentileOperation: OperationDefinition< : ofName( indexPattern.getFieldByName(currentColumn.sourceField)?.displayName || currentColumn.sourceField, - inputValueAsNumber, + Number(value), currentColumn.timeShift ), params: { ...currentColumn.params, - percentile: inputValueAsNumber, + percentile: Number(value), }, } as PercentileIndexPatternColumn, }, }); }, - { skipFirstRender: true }, - 256, - [inputValue] + [updateLayer, layer, columnId, currentColumn, indexPattern] + ); + const { inputValue, handleInputChange: handleInputChangeWithoutValidation } = useDebouncedValue< + string | undefined + >({ + onChange, + value: String(currentColumn.params.percentile), + }); + const inputValueIsValid = isValidNumber(inputValue, true, 99, 1); + + const handleInputChange: EuiRangeProps['onChange'] = useCallback( + (e) => handleInputChangeWithoutValidation(String(e.currentTarget.value)), + [handleInputChangeWithoutValidation] ); - const handleInputChange = useCallback((e: React.ChangeEvent) => { - const val = String(e.target.value); - setInputValue(val); - }, []); return ( - );