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] Add onRefresh handler #1577

Merged
merged 11 commits into from
Feb 25, 2019
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## [`master`](https://github.com/elastic/eui/tree/master)

- Added `onRefresh` option for `EuiSuperDatePicker` ([#1577](https://github.com/elastic/eui/pull/1577))
- Converted `EuiToggle` to TypeScript ([#1570](https://github.com/elastic/eui/pull/1570))
- Added type definitions for `EuiButtonGroup`,`EuiButtonToggle`, `EuiFilterButton`, `EuiFilterGroup`, and `EuiFilterSelectItem` ([#1570](https://github.com/elastic/eui/pull/1570))
- Added `displayOnly` prop to EuiFormRow ([#1582](https://github.com/elastic/eui/pull/1582))
Expand Down
9 changes: 9 additions & 0 deletions src-docs/src/views/date_picker/super_date_picker.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ export default class extends Component {
}, this.startLoading);
}

onRefresh = ({ start, end, refreshInterval }) => {
return new Promise((resolve) => {
setTimeout(resolve, 100);
}).then(() => {
console.log(start, end, refreshInterval);
chandlerprall marked this conversation as resolved.
Show resolved Hide resolved
});
}

onStartInputChange = e => {
this.setState({
start: e.target.value,
Expand Down Expand Up @@ -168,6 +176,7 @@ export default class extends Component {
start={this.state.start}
end={this.state.end}
onTimeChange={this.onTimeChange}
onRefresh={this.onRefresh}
isPaused={this.state.isPaused}
refreshInterval={this.state.refreshInterval}
onRefreshChange={this.onRefreshChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ exports[`EuiSuperDatePicker is rendered 1`] = `
isLoading={false}
prepend={
<EuiQuickSelectPopover
applyRefreshInterval={null}
applyTime={[Function]}
commonlyUsedRanges={
Array [
Expand Down
22 changes: 22 additions & 0 deletions src/components/date_picker/super_date_picker/async_interval.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export class AsyncInterval {
timeoutId = null;
isStopped = false;

constructor(fn, refreshInterval) {
this.setAsyncInterval(fn, refreshInterval);
}

setAsyncInterval = (fn, ms) => {
if (!this.isStopped) {
this.timeoutId = window.setTimeout(async () => {
this.__pendingFn = await fn();
this.setAsyncInterval(fn, ms);
sorenlouv marked this conversation as resolved.
Show resolved Hide resolved
}, ms);
}
};

stop = () => {
this.isStopped = true;
window.clearTimeout(this.timeoutId);
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { AsyncInterval } from './async_interval';
import { times } from 'lodash';

describe('AsyncInterval', () => {
beforeEach(() => {
jest.useFakeTimers();
});

afterEach(() => {
jest.useRealTimers();
});

// Advances time and awaits any pending promises after every 100ms
// This helper makes it easier to advance time without worrying
// whether tasks are still lingering on the event loop
async function andvanceTimerAndAwaitFn(instance, ms) {
const iterations = times(Math.floor(ms / 100));
const remainder = ms % 100;
// eslint-disable-next-line no-unused-vars
for (const item of iterations) {
await instance.__pendingFn;
jest.advanceTimersByTime(100);
await instance.__pendingFn;
}
jest.advanceTimersByTime(remainder);
await instance.__pendingFn;
}

describe('when creating a 1000ms interval', async () => {
let instance;
let spy;
beforeEach(() => {
spy = jest.fn();
instance = new AsyncInterval(spy, 1000);
});

it('should not call fn immediately', async () => {
await andvanceTimerAndAwaitFn(instance, 0);
expect(spy).toHaveBeenCalledTimes(0);
});

it('should have called fn once after 1000ms', async () => {
await andvanceTimerAndAwaitFn(instance, 1000);
expect(spy).toHaveBeenCalledTimes(1);
});

it('should have called fn twice after 2000ms', async () => {
await andvanceTimerAndAwaitFn(instance, 2000);
expect(spy).toHaveBeenCalledTimes(2);
});

it('should have called fn three times after 3000ms', async () => {
await andvanceTimerAndAwaitFn(instance, 3000);
expect(spy).toHaveBeenCalledTimes(3);
});

it('should not call fn after stop has been invoked', async () => {
await andvanceTimerAndAwaitFn(instance, 1000);
expect(spy).toHaveBeenCalledTimes(1);
instance.stop();
await andvanceTimerAndAwaitFn(instance, 1000);
expect(spy).toHaveBeenCalledTimes(1);
});
});

describe('when creating a 1000ms interval that calls a fn that takes 2000ms to complete', async () => {
let instance;
let spy;
beforeEach(() => {
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
spy = jest.fn(async () => await sleep(2000));
instance = new AsyncInterval(spy, 1000);
});

it('should not call fn immediately', async () => {
await andvanceTimerAndAwaitFn(instance, 0);
expect(spy).toHaveBeenCalledTimes(0);
});

it('should have called fn once after 1000ms', async () => {
await andvanceTimerAndAwaitFn(instance, 1000);
expect(spy).toHaveBeenCalledTimes(1);
});

it('should have called fn twice after 4000ms', async () => {
await andvanceTimerAndAwaitFn(instance, 4000);
expect(spy).toHaveBeenCalledTimes(2);
});

it('should have called fn tree times after 7000ms', async () => {
await andvanceTimerAndAwaitFn(instance, 7000);
expect(spy).toHaveBeenCalledTimes(3);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { EuiDatePopoverButton } from './date_popover/date_popover_button';
import { EuiDatePickerRange } from '../date_picker_range';
import { EuiFormControlLayout } from '../../form';
import { EuiFlexGroup, EuiFlexItem } from '../../flex';
import { AsyncInterval } from './async_interval';

function isRangeInvalid(start, end) {
if (start === 'now' && end === 'now') {
Expand Down Expand Up @@ -62,6 +63,14 @@ export class EuiSuperDatePicker extends Component {
*/
onRefreshChange: PropTypes.func,

/**
* Callback for when the refresh interval is fired. Called with { start, end, refreshInterval }
sorenlouv marked this conversation as resolved.
Show resolved Hide resolved
* EuiSuperDatePicker will only manage a refresh interval timer when onRefresh callback is supplied
* If a promise is returned, the next refresh interval will not start until the promise has resolved.
* If the promise rejects the refresh interval will stop and the error thrown
*/
onRefresh: PropTypes.func,
sorenlouv marked this conversation as resolved.
Show resolved Hide resolved

/**
* 'start' and 'end' must be string as either datemath (e.g.: now, now-15m, now-15m/m) or
* absolute date in the format 'YYYY-MM-DDTHH:mm:ss.sssZ'
Expand Down Expand Up @@ -168,6 +177,16 @@ export class EuiSuperDatePicker extends Component {
}
}

componentDidMount = () => {
if(!this.props.isPaused) {
this.startInterval(this.props.refreshInterval);
}
}

componentWillUnmount = () => {
this.stopInterval();
}

setStart = (start) => {
this.setTime({ start, end: this.state.end });
}
Expand Down Expand Up @@ -221,6 +240,30 @@ export class EuiSuperDatePicker extends Component {
this.setState({ isEndDatePopoverOpen: false });
}

onRefreshChange = ({ refreshInterval, isPaused }) => {
this.stopInterval();
if(!isPaused) {
this.startInterval(refreshInterval);
}
if(this.props.onRefreshChange) {
this.props.onRefreshChange({ refreshInterval, isPaused });
}
}

stopInterval = () => {
if(this.asyncInterval) {
this.asyncInterval.stop();
}
}

startInterval = (refreshInterval) => {
const { start, end, onRefresh } = this.props;
if(onRefresh) {
const handler = () => onRefresh({ start, end, refreshInterval });
this.asyncInterval = new AsyncInterval(handler, refreshInterval);
}
}

renderDatePickerRange = () => {
const {
start,
Expand Down Expand Up @@ -329,7 +372,7 @@ export class EuiSuperDatePicker extends Component {
applyTime={this.applyQuickTime}
start={this.props.start}
end={this.props.end}
applyRefreshInterval={this.props.onRefreshChange}
applyRefreshInterval={this.props.onRefreshChange ? this.onRefreshChange : null}
isPaused={this.props.isPaused}
refreshInterval={this.props.refreshInterval}
commonlyUsedRanges={this.props.commonlyUsedRanges}
Expand Down