Skip to content

Commit

Permalink
[EuiSuperDatePicker] Add onRefresh handler (#1577)
Browse files Browse the repository at this point in the history
  • Loading branch information
sorenlouv authored Feb 25, 2019
1 parent babf10c commit 644fd63
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 1 deletion.
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);
});
}

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);
}, 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 }
* 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,

/**
* '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

0 comments on commit 644fd63

Please sign in to comment.