Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EuiSuperDatePicker][EuiAutoRefresh][EuiRefreshInterval] Allow setting a minimum interval in milliseconds #7516

Merged
merged 5 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelogs/upcoming/7516.md
Original file line number Diff line number Diff line change
@@ -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
43 changes: 43 additions & 0 deletions src/components/date_picker/auto_refresh/auto_refresh.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<EuiAutoRefresh
isPaused={false}
refreshInterval={1000}
minInterval={3000}
onRefreshChange={onRefreshChange}
/>
);
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(
<EuiAutoRefresh
Expand Down
4 changes: 4 additions & 0 deletions src/components/date_picker/auto_refresh/auto_refresh.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const EuiAutoRefresh: FunctionComponent<EuiAutoRefreshProps> = ({
isDisabled,
isPaused = true,
refreshInterval = 1000,
minInterval = 0,
readOnly = true,
...rest
}) => {
Expand Down Expand Up @@ -90,6 +91,7 @@ export const EuiAutoRefresh: FunctionComponent<EuiAutoRefreshProps> = ({
onRefreshChange={onRefreshChange}
isPaused={isPaused}
refreshInterval={refreshInterval}
minInterval={minInterval}
intervalUnits={intervalUnits}
/>
</EuiInputPopover>
Expand All @@ -115,6 +117,7 @@ export const EuiAutoRefreshButton: FunctionComponent<
isDisabled,
isPaused = true,
refreshInterval = 1000,
minInterval = 0,
shortHand = false,
size = 's',
color = 'text',
Expand Down Expand Up @@ -168,6 +171,7 @@ export const EuiAutoRefreshButton: FunctionComponent<
onRefreshChange={onRefreshChange}
isPaused={isPaused}
refreshInterval={refreshInterval}
minInterval={minInterval}
intervalUnits={intervalUnits}
/>
</EuiPopover>
Expand Down
54 changes: 39 additions & 15 deletions src/components/date_picker/auto_refresh/refresh_interval.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
Expand All @@ -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 = {
/**
Expand All @@ -77,21 +86,26 @@ 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).
*
* 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<
Expand All @@ -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();
Expand All @@ -123,9 +141,11 @@ export class EuiRefreshInterval extends Component<
};

onUnitsChange: ChangeEventHandler<HTMLSelectElement> = (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
);
Expand All @@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -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 (
<RenderI18nTimeOptions>
Expand Down Expand Up @@ -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)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -497,6 +502,7 @@ export class EuiSuperDatePickerInternal extends Component<
timeOptions,
dateFormat,
refreshInterval,
refreshMinInterval,
refreshIntervalUnits,
isPaused,
isDisabled,
Expand All @@ -511,6 +517,7 @@ export class EuiSuperDatePickerInternal extends Component<
const autoRefreshAppend: EuiFormControlLayoutProps['append'] = !isPaused ? (
<EuiAutoRefreshButton
refreshInterval={refreshInterval}
minInterval={refreshMinInterval}
intervalUnits={refreshIntervalUnits}
isDisabled={!!isDisabled}
isPaused={isPaused}
Expand Down Expand Up @@ -671,6 +678,7 @@ export class EuiSuperDatePickerInternal extends Component<
isPaused,
onRefreshChange,
refreshInterval,
refreshMinInterval,
refreshIntervalUnits,
showUpdateButton,
'data-test-subj': dataTestSubj,
Expand Down Expand Up @@ -700,6 +708,7 @@ export class EuiSuperDatePickerInternal extends Component<
<EuiAutoRefresh
isPaused={isPaused}
refreshInterval={refreshInterval}
minInterval={refreshMinInterval}
intervalUnits={refreshIntervalUnits}
onRefreshChange={this.onRefreshChange}
fullWidth={width === 'full'}
Expand Down
Loading