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

[8.x] [Controls] Debounce time slider selections (#201885) #204979

Merged
merged 1 commit into from
Dec 19, 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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
*/

import React from 'react';
import { useMemo, useEffect, useState } from 'react';
import { debounce } from 'lodash';
import { EuiButtonIcon, EuiRangeTick, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';

import { TimeSliderStrings } from './time_slider_strings';
Expand All @@ -27,29 +29,63 @@ interface Props {
compressed: boolean;
}

export function TimeSliderPopoverContent(props: Props) {
const rangeInput = props.isAnchored ? (
export function TimeSliderPopoverContent({
isAnchored,
setIsAnchored,
value,
onChange,
stepSize,
ticks,
timeRangeMin,
timeRangeMax,
compressed,
}: Props) {
const [displayedValue, setDisplayedValue] = useState<Timeslice>(value);

const debouncedOnChange = useMemo(
() =>
debounce((updateTimeslice: Timeslice | undefined) => {
onChange(updateTimeslice);
}, 750),
[onChange]
);

/**
* The following `useEffect` ensures that the changes to the value that come from the embeddable (for example,
* from the `clear` button on the dashboard) are reflected in the displayed value
*/
useEffect(() => {
setDisplayedValue(value);
}, [value]);

const rangeInput = isAnchored ? (
<TimeSliderAnchoredRange
value={props.value}
onChange={props.onChange}
stepSize={props.stepSize}
ticks={props.ticks}
timeRangeMin={props.timeRangeMin}
timeRangeMax={props.timeRangeMax}
compressed={props.compressed}
value={[displayedValue[0] || timeRangeMin, displayedValue[1] || timeRangeMax]}
onChange={(newValue) => {
setDisplayedValue(newValue as Timeslice);
debouncedOnChange(newValue);
}}
stepSize={stepSize}
ticks={ticks}
timeRangeMin={timeRangeMin}
timeRangeMax={timeRangeMax}
compressed={compressed}
/>
) : (
<TimeSliderSlidingWindowRange
value={props.value}
onChange={props.onChange}
stepSize={props.stepSize}
ticks={props.ticks}
timeRangeMin={props.timeRangeMin}
timeRangeMax={props.timeRangeMax}
compressed={props.compressed}
value={[displayedValue[0] || timeRangeMin, displayedValue[1] || timeRangeMax]}
onChange={(newValue) => {
setDisplayedValue(newValue as Timeslice);
debouncedOnChange(newValue);
}}
stepSize={stepSize}
ticks={ticks}
timeRangeMin={timeRangeMin}
timeRangeMax={timeRangeMax}
compressed={compressed}
/>
);
const anchorStartToggleButtonLabel = props.isAnchored
const anchorStartToggleButtonLabel = isAnchored
? TimeSliderStrings.control.getUnpinStart()
: TimeSliderStrings.control.getPinStart();

Expand All @@ -59,17 +95,24 @@ export function TimeSliderPopoverContent(props: Props) {
gutterSize="none"
data-test-subj="timeSlider-popoverContents"
responsive={false}
onMouseUp={() => {
// when the pin is dropped (on mouse up), cancel any pending debounced changes and force the change
// in value to happen instantly (which, in turn, will re-calculate the from/to for the slider due to
// the `useEffect` above.
debouncedOnChange.cancel();
onChange(displayedValue);
}}
>
<EuiFlexItem grow={false}>
<EuiToolTip content={anchorStartToggleButtonLabel} position="left">
<EuiButtonIcon
iconType={props.isAnchored ? 'pinFilled' : 'pin'}
iconType={isAnchored ? 'pinFilled' : 'pin'}
onClick={() => {
const nextIsAnchored = !props.isAnchored;
const nextIsAnchored = !isAnchored;
if (nextIsAnchored) {
props.onChange([props.timeRangeMin, props.value[1]]);
onChange([timeRangeMin, value[1]]);
}
props.setIsAnchored(nextIsAnchored);
setIsAnchored(nextIsAnchored);
}}
aria-label={anchorStartToggleButtonLabel}
data-test-subj="timeSlider__anchorStartToggleButton"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,6 @@ export const getTimesliderControlFactory = (): ControlFactory<
Component: (controlPanelClassNames) => {
const [isAnchored, isPopoverOpen, timeRangeMeta, timeslice] =
useBatchedPublishingSubjects(isAnchored$, isPopoverOpen$, timeRangeMeta$, timeslice$);

useEffect(() => {
return () => {
cleanupTimeRangeSubscription();
Expand All @@ -284,6 +283,9 @@ export const getTimesliderControlFactory = (): ControlFactory<
const to = useMemo(() => {
return timeslice ? timeslice[TO_INDEX] : timeRangeMeta.timeRangeMax;
}, [timeslice, timeRangeMeta.timeRangeMax]);
const value: Timeslice = useMemo(() => {
return [from, to];
}, [from, to]);

return (
<EuiInputPopover
Expand All @@ -306,7 +308,7 @@ export const getTimesliderControlFactory = (): ControlFactory<
<TimeSliderPopoverContent
isAnchored={typeof isAnchored === 'boolean' ? isAnchored : false}
setIsAnchored={setIsAnchored}
value={[from, to]}
value={value}
onChange={onChange}
stepSize={timeRangeMeta.stepSize}
ticks={timeRangeMeta.ticks}
Expand Down
Loading