diff --git a/changelogs/upcoming/7516.md b/changelogs/upcoming/7516.md new file mode 100644 index 00000000000..f05e82de4e9 --- /dev/null +++ b/changelogs/upcoming/7516.md @@ -0,0 +1,2 @@ +- Updated `EuiSuperDatePicker` with a new `refreshMinInterval` prop, which accepts a minimum number in milliseconds +- Updated `EuiAutoRefresh` and `EuiRefreshInterval` with a new `minInterval` prop, which accepts a minimum number in milliseconds diff --git a/src/components/date_picker/auto_refresh/auto_refresh.test.tsx b/src/components/date_picker/auto_refresh/auto_refresh.test.tsx index 9edbcc3f546..fe6a4ff158f 100644 --- a/src/components/date_picker/auto_refresh/auto_refresh.test.tsx +++ b/src/components/date_picker/auto_refresh/auto_refresh.test.tsx @@ -42,6 +42,49 @@ describe('EuiAutoRefresh', () => { expect(container.firstChild).toMatchSnapshot(); }); + test('minInterval renders an invalid warning on the number input', () => { + const onRefreshChange = jest.fn(); + const { getByRole, getByTestSubject } = render( + + ); + const getNumberInput = () => + getByTestSubject('superDatePickerRefreshIntervalInput'); + const getUnitSelect = () => + getByTestSubject('superDatePickerRefreshIntervalUnitsSelect'); + + fireEvent.click(getByRole('button')); + expect(getNumberInput()).toBeInvalid(); + + fireEvent.change(getUnitSelect(), { target: { value: 'm' } }); + expect(onRefreshChange).toHaveBeenLastCalledWith({ + refreshInterval: 60000, + intervalUnits: 'm', + isPaused: false, + }); + expect(getNumberInput()).toBeValid(); + + fireEvent.change(getUnitSelect(), { target: { value: 's' } }); + expect(onRefreshChange).toHaveBeenLastCalledWith({ + refreshInterval: 3000, // Should pass back the minimum instead of the current 1000 value + intervalUnits: 's', + isPaused: false, + }); + expect(getNumberInput()).toBeInvalid(); + + fireEvent.change(getNumberInput(), { target: { value: 5 } }); + expect(onRefreshChange).toHaveBeenLastCalledWith({ + refreshInterval: 5000, + intervalUnits: 's', + isPaused: false, + }); + expect(getNumberInput()).toBeValid(); + }); + test('intervalUnits forces rendering in the provided units', () => { const { getByLabelText, getByRole, getByTestSubject } = render( = ({ isDisabled, isPaused = true, refreshInterval = 1000, + minInterval = 0, readOnly = true, ...rest }) => { @@ -90,6 +91,7 @@ export const EuiAutoRefresh: FunctionComponent = ({ onRefreshChange={onRefreshChange} isPaused={isPaused} refreshInterval={refreshInterval} + minInterval={minInterval} intervalUnits={intervalUnits} /> @@ -115,6 +117,7 @@ export const EuiAutoRefreshButton: FunctionComponent< isDisabled, isPaused = true, refreshInterval = 1000, + minInterval = 0, shortHand = false, size = 's', color = 'text', @@ -168,6 +171,7 @@ export const EuiAutoRefreshButton: FunctionComponent< onRefreshChange={onRefreshChange} isPaused={isPaused} refreshInterval={refreshInterval} + minInterval={minInterval} intervalUnits={intervalUnits} /> diff --git a/src/components/date_picker/auto_refresh/refresh_interval.tsx b/src/components/date_picker/auto_refresh/refresh_interval.tsx index 5c86a7ef997..9de28b0775e 100644 --- a/src/components/date_picker/auto_refresh/refresh_interval.tsx +++ b/src/components/date_picker/auto_refresh/refresh_interval.tsx @@ -30,10 +30,10 @@ const MILLISECONDS_IN_SECOND = 1000; const MILLISECONDS_IN_MINUTE = MILLISECONDS_IN_SECOND * 60; const MILLISECONDS_IN_HOUR = MILLISECONDS_IN_MINUTE * 60; -function fromMilliseconds( +const fromMilliseconds = ( milliseconds: Milliseconds, unit?: RefreshUnitsOptions -): EuiRefreshIntervalState { +): EuiRefreshIntervalState => { const round = (value: number) => parseFloat(value.toFixed(2)); if (unit === 'h' || (!unit && milliseconds > MILLISECONDS_IN_HOUR)) { return { @@ -53,9 +53,9 @@ function fromMilliseconds( units: 's', value: round(milliseconds / MILLISECONDS_IN_SECOND), }; -} +}; -function toMilliseconds(units: RefreshUnitsOptions, value: Milliseconds) { +const toMilliseconds = (units: RefreshUnitsOptions, value: Milliseconds) => { switch (units) { case 'h': return Math.round(value * MILLISECONDS_IN_HOUR); @@ -65,7 +65,16 @@ function toMilliseconds(units: RefreshUnitsOptions, value: Milliseconds) { default: return Math.round(value * MILLISECONDS_IN_SECOND); } -} +}; + +const getMinInterval = ( + minInterval?: Milliseconds, + unit?: RefreshUnitsOptions +): number => { + if (!minInterval) return 0; + const { value } = fromMilliseconds(minInterval, unit); + return Math.floor(value || 0); +}; export type EuiRefreshIntervalProps = { /** @@ -77,9 +86,9 @@ export type EuiRefreshIntervalProps = { */ refreshInterval?: Milliseconds; /** - * Passes back the updated state of `isPaused` `refreshInterval`, and `intervalUnits`. + * Allows specifying a minimum interval in milliseconds */ - onRefreshChange: ApplyRefreshInterval; + minInterval?: Milliseconds; /** * By default, refresh interval units will be rounded up to next largest unit of time * (for example, 90 seconds will become 2m). @@ -87,11 +96,16 @@ export type EuiRefreshIntervalProps = { * If you do not want this behavior, you can manually control the rendered unit via this prop. */ intervalUnits?: RefreshUnitsOptions; + /** + * Passes back the updated state of `isPaused`, `refreshInterval`, and `intervalUnits`. + */ + onRefreshChange: ApplyRefreshInterval; }; interface EuiRefreshIntervalState { value: number | ''; units: RefreshUnitsOptions; + min?: Milliseconds; } export class EuiRefreshInterval extends Component< @@ -101,12 +115,16 @@ export class EuiRefreshInterval extends Component< static defaultProps = { isPaused: true, refreshInterval: 1000, + minInterval: 0, }; - state: EuiRefreshIntervalState = fromMilliseconds( - this.props.refreshInterval || 0, - this.props.intervalUnits - ); + state: EuiRefreshIntervalState = { + ...fromMilliseconds( + this.props.refreshInterval || 0, + this.props.intervalUnits + ), + min: getMinInterval(this.props.minInterval, this.props.intervalUnits), + }; generateId = htmlIdGenerator(); legendId = this.generateId(); @@ -123,9 +141,11 @@ export class EuiRefreshInterval extends Component< }; onUnitsChange: ChangeEventHandler = (event) => { + const units = event.target.value as RefreshUnitsOptions; this.setState( { - units: event.target.value as RefreshUnitsOptions, + units, + min: getMinInterval(this.props.minInterval, units), }, this.applyRefreshInterval ); @@ -151,7 +171,7 @@ export class EuiRefreshInterval extends Component< }; applyRefreshInterval = () => { - const { onRefreshChange, isPaused } = this.props; + const { onRefreshChange, isPaused, minInterval } = this.props; const { units, value } = this.state; if (value === '') { return; @@ -160,7 +180,10 @@ export class EuiRefreshInterval extends Component< return; } - const refreshInterval = toMilliseconds(units, value); + const refreshInterval = Math.max( + toMilliseconds(units, value), + minInterval || 0 + ); onRefreshChange({ refreshInterval, @@ -221,7 +244,7 @@ export class EuiRefreshInterval extends Component< render() { const { isPaused } = this.props; - const { value, units } = this.state; + const { value, units, min } = this.state; return ( @@ -255,6 +278,7 @@ export class EuiRefreshInterval extends Component< compressed fullWidth value={value} + min={min} onChange={this.onValueChange} onKeyDown={this.handleKeyDown} isInvalid={!isPaused && (value === '' || value <= 0)} diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap b/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap index c1be9589f98..54dbcc3c5cb 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap +++ b/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap @@ -350,6 +350,7 @@ exports[`EuiQuickSelectPanels customQuickSelectPanels should render custom panel class="euiFieldNumber euiFieldNumber--fullWidth euiFieldNumber--compressed" data-test-subj="superDatePickerRefreshIntervalInput" disabled="" + min="0" step="any" type="number" value="0" diff --git a/src/components/date_picker/super_date_picker/super_date_picker.tsx b/src/components/date_picker/super_date_picker/super_date_picker.tsx index ab94d756605..d7db318dd59 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.tsx +++ b/src/components/date_picker/super_date_picker/super_date_picker.tsx @@ -148,6 +148,11 @@ export type EuiSuperDatePickerProps = CommonProps & { * @default 1000 */ refreshInterval?: Milliseconds; + /** + * Minimum refresh interval in milliseconds + * @default 0 + */ + refreshMinInterval?: Milliseconds; /** * By default, refresh interval units will be rounded up to next largest unit of time * (for example, 90 seconds will become 2m). @@ -497,6 +502,7 @@ export class EuiSuperDatePickerInternal extends Component< timeOptions, dateFormat, refreshInterval, + refreshMinInterval, refreshIntervalUnits, isPaused, isDisabled, @@ -511,6 +517,7 @@ export class EuiSuperDatePickerInternal extends Component< const autoRefreshAppend: EuiFormControlLayoutProps['append'] = !isPaused ? (