mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Canvas] Fixes bugs with autoplay and refresh (#53149)
* Fixes bugs with autoplay and refresh * Fix typecheck Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
fc948a0c8e
commit
89e4daf5bd
5 changed files with 351 additions and 26 deletions
|
@ -92,7 +92,7 @@ export function setFullscreen(payload: boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
export function setAutoplayInterval(payload: string) {
|
||||
export function setAutoplayInterval(payload: string | null) {
|
||||
const appState = getAppState();
|
||||
const appValue = appState[AppStateKeys.AUTOPLAY_INTERVAL];
|
||||
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
jest.mock('../../../lib/app_state');
|
||||
jest.mock('../../../lib/router_provider');
|
||||
|
||||
import { workpadAutoplay } from '../workpad_autoplay';
|
||||
import { setAutoplayInterval } from '../../../lib/app_state';
|
||||
import { createTimeInterval } from '../../../lib/time_interval';
|
||||
// @ts-ignore Untyped local
|
||||
import { routerProvider } from '../../../lib/router_provider';
|
||||
|
||||
const next = jest.fn();
|
||||
const dispatch = jest.fn();
|
||||
const getState = jest.fn();
|
||||
const routerMock = { navigateTo: jest.fn() };
|
||||
routerProvider.mockReturnValue(routerMock);
|
||||
|
||||
const middleware = workpadAutoplay({ dispatch, getState })(next);
|
||||
|
||||
const workpadState = {
|
||||
persistent: {
|
||||
workpad: {
|
||||
id: 'workpad-id',
|
||||
pages: ['page1', 'page2', 'page3'],
|
||||
page: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const autoplayState = {
|
||||
...workpadState,
|
||||
transient: {
|
||||
autoplay: {
|
||||
inFlight: false,
|
||||
enabled: true,
|
||||
interval: 5000,
|
||||
},
|
||||
fullscreen: true,
|
||||
},
|
||||
};
|
||||
|
||||
const autoplayDisabledState = {
|
||||
...workpadState,
|
||||
transient: {
|
||||
autoplay: {
|
||||
inFlight: false,
|
||||
enabled: false,
|
||||
interval: 5000,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const action = {};
|
||||
|
||||
describe('workpad autoplay middleware', () => {
|
||||
beforeEach(() => {
|
||||
dispatch.mockClear();
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('app state', () => {
|
||||
it('sets the app state to the interval from state when enabled', () => {
|
||||
getState.mockReturnValue(autoplayState);
|
||||
middleware(action);
|
||||
|
||||
expect(setAutoplayInterval).toBeCalledWith(
|
||||
createTimeInterval(autoplayState.transient.autoplay.interval)
|
||||
);
|
||||
});
|
||||
|
||||
it('sets the app state to null when not enabled', () => {
|
||||
getState.mockReturnValue(autoplayDisabledState);
|
||||
middleware(action);
|
||||
|
||||
expect(setAutoplayInterval).toBeCalledWith(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('autoplay navigation', () => {
|
||||
it('navigates forward after interval', () => {
|
||||
jest.useFakeTimers();
|
||||
getState.mockReturnValue(autoplayState);
|
||||
middleware(action);
|
||||
|
||||
jest.advanceTimersByTime(autoplayState.transient.autoplay.interval + 1);
|
||||
|
||||
expect(routerMock.navigateTo).toBeCalledWith('loadWorkpad', {
|
||||
id: workpadState.persistent.workpad.id,
|
||||
page: workpadState.persistent.workpad.page + 2, // (index + 1) + 1 more for 1 indexed page number
|
||||
});
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('navigates from last page back to front', () => {
|
||||
jest.useFakeTimers();
|
||||
const onLastPageState = { ...autoplayState };
|
||||
onLastPageState.persistent.workpad.page = onLastPageState.persistent.workpad.pages.length - 1;
|
||||
|
||||
getState.mockReturnValue(autoplayState);
|
||||
middleware(action);
|
||||
|
||||
jest.advanceTimersByTime(autoplayState.transient.autoplay.interval + 1);
|
||||
|
||||
expect(routerMock.navigateTo).toBeCalledWith('loadWorkpad', {
|
||||
id: workpadState.persistent.workpad.id,
|
||||
page: 1,
|
||||
});
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('continues autoplaying', () => {
|
||||
jest.useFakeTimers();
|
||||
getState.mockReturnValue(autoplayState);
|
||||
middleware(action);
|
||||
|
||||
jest.advanceTimersByTime(autoplayState.transient.autoplay.interval * 2 + 1);
|
||||
expect(routerMock.navigateTo).toBeCalledTimes(2);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('does not reset timer between middleware calls', () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
getState.mockReturnValue(autoplayState);
|
||||
middleware(action);
|
||||
|
||||
// Advance until right before timeout
|
||||
jest.advanceTimersByTime(autoplayState.transient.autoplay.interval - 1);
|
||||
|
||||
// Run middleware again
|
||||
middleware(action);
|
||||
|
||||
// Advance timer
|
||||
jest.advanceTimersByTime(1);
|
||||
|
||||
expect(routerMock.navigateTo).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
jest.mock('../../../legacy');
|
||||
jest.mock('ui/new_platform'); // actions/elements has some dependencies on ui/new_platform.
|
||||
jest.mock('../../../lib/app_state');
|
||||
|
||||
import { workpadRefresh } from '../workpad_refresh';
|
||||
import { inFlightComplete } from '../../actions/resolved_args';
|
||||
// @ts-ignore untyped local
|
||||
import { setRefreshInterval } from '../../actions/workpad';
|
||||
import { setRefreshInterval as setAppStateRefreshInterval } from '../../../lib/app_state';
|
||||
|
||||
import { createTimeInterval } from '../../../lib/time_interval';
|
||||
|
||||
const next = jest.fn();
|
||||
const dispatch = jest.fn();
|
||||
const getState = jest.fn();
|
||||
|
||||
const middleware = workpadRefresh({ dispatch, getState })(next);
|
||||
|
||||
const refreshState = {
|
||||
transient: {
|
||||
refresh: {
|
||||
interval: 5000,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const noRefreshState = {
|
||||
transient: {
|
||||
refresh: {
|
||||
interval: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const inFlightState = {
|
||||
transient: {
|
||||
refresh: {
|
||||
interval: 5000,
|
||||
},
|
||||
inFlight: true,
|
||||
},
|
||||
};
|
||||
|
||||
describe('workpad refresh middleware', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('onInflightComplete', () => {
|
||||
it('refreshes if interval gt 0', () => {
|
||||
jest.useFakeTimers();
|
||||
getState.mockReturnValue(refreshState);
|
||||
|
||||
middleware(inFlightComplete());
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(dispatch).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not reset interval if another action occurs', () => {
|
||||
jest.useFakeTimers();
|
||||
getState.mockReturnValue(refreshState);
|
||||
|
||||
middleware(inFlightComplete());
|
||||
|
||||
jest.advanceTimersByTime(refreshState.transient.refresh.interval - 1);
|
||||
|
||||
expect(dispatch).not.toHaveBeenCalled();
|
||||
middleware(inFlightComplete());
|
||||
|
||||
jest.advanceTimersByTime(1);
|
||||
|
||||
expect(dispatch).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not refresh if interval is 0', () => {
|
||||
jest.useFakeTimers();
|
||||
getState.mockReturnValue(noRefreshState);
|
||||
|
||||
middleware(inFlightComplete());
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(dispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('setRefreshInterval', () => {
|
||||
it('does nothing if refresh interval is unchanged', () => {
|
||||
getState.mockReturnValue(refreshState);
|
||||
|
||||
jest.useFakeTimers();
|
||||
const interval = 1;
|
||||
middleware(setRefreshInterval(interval));
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(setAppStateRefreshInterval).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('sets the app refresh interval', () => {
|
||||
getState.mockReturnValue(noRefreshState);
|
||||
next.mockImplementation(() => {
|
||||
getState.mockReturnValue(refreshState);
|
||||
});
|
||||
|
||||
jest.useFakeTimers();
|
||||
const interval = 1;
|
||||
middleware(setRefreshInterval(interval));
|
||||
|
||||
expect(setAppStateRefreshInterval).toBeCalledWith(createTimeInterval(interval));
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
it('starts a refresh for the new interval', () => {
|
||||
getState.mockReturnValue(refreshState);
|
||||
jest.useFakeTimers();
|
||||
|
||||
const interval = 1000;
|
||||
|
||||
middleware(inFlightComplete());
|
||||
|
||||
jest.runTimersToTime(refreshState.transient.refresh.interval - 1);
|
||||
expect(dispatch).not.toBeCalled();
|
||||
|
||||
getState.mockReturnValue(noRefreshState);
|
||||
next.mockImplementation(() => {
|
||||
getState.mockReturnValue(refreshState);
|
||||
});
|
||||
middleware(setRefreshInterval(interval));
|
||||
jest.runTimersToTime(1);
|
||||
|
||||
expect(dispatch).not.toBeCalled();
|
||||
|
||||
jest.runTimersToTime(interval);
|
||||
expect(dispatch).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('inFlight in progress', () => {
|
||||
it('requeues the refresh when inflight is active', () => {
|
||||
jest.useFakeTimers();
|
||||
getState.mockReturnValue(inFlightState);
|
||||
|
||||
middleware(inFlightComplete());
|
||||
jest.runTimersToTime(refreshState.transient.refresh.interval);
|
||||
|
||||
expect(dispatch).not.toBeCalled();
|
||||
|
||||
getState.mockReturnValue(refreshState);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(dispatch).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -4,16 +4,18 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { inFlightComplete } from '../actions/resolved_args';
|
||||
import { Middleware } from 'redux';
|
||||
import { State } from '../../../types';
|
||||
import { getFullscreen } from '../selectors/app';
|
||||
import { getInFlight } from '../selectors/resolved_args';
|
||||
import { getWorkpad, getPages, getSelectedPageIndex, getAutoplay } from '../selectors/workpad';
|
||||
// @ts-ignore untyped local
|
||||
import { routerProvider } from '../../lib/router_provider';
|
||||
import { setAutoplayInterval } from '../../lib/app_state';
|
||||
import { createTimeInterval } from '../../lib/time_interval';
|
||||
|
||||
export const workpadAutoplay = ({ getState }) => next => {
|
||||
let playTimeout;
|
||||
export const workpadAutoplay: Middleware<{}, State> = ({ getState }) => next => {
|
||||
let playTimeout: number | undefined;
|
||||
let displayInterval = 0;
|
||||
|
||||
const router = routerProvider();
|
||||
|
@ -42,18 +44,22 @@ export const workpadAutoplay = ({ getState }) => next => {
|
|||
}
|
||||
}
|
||||
|
||||
stopAutoUpdate();
|
||||
startDelayedUpdate();
|
||||
}
|
||||
|
||||
function stopAutoUpdate() {
|
||||
clearTimeout(playTimeout); // cancel any pending update requests
|
||||
playTimeout = undefined;
|
||||
}
|
||||
|
||||
function startDelayedUpdate() {
|
||||
stopAutoUpdate();
|
||||
playTimeout = setTimeout(() => {
|
||||
updateWorkpad();
|
||||
}, displayInterval);
|
||||
if (!playTimeout) {
|
||||
stopAutoUpdate();
|
||||
playTimeout = window.setTimeout(() => {
|
||||
updateWorkpad();
|
||||
}, displayInterval);
|
||||
}
|
||||
}
|
||||
|
||||
return action => {
|
||||
|
@ -68,21 +74,14 @@ export const workpadAutoplay = ({ getState }) => next => {
|
|||
if (autoplay.enabled) {
|
||||
setAutoplayInterval(createTimeInterval(autoplay.interval));
|
||||
} else {
|
||||
setAutoplayInterval(0);
|
||||
setAutoplayInterval(null);
|
||||
}
|
||||
|
||||
// when in-flight requests are finished, update the workpad after a given delay
|
||||
if (action.type === inFlightComplete.toString() && shouldPlay) {
|
||||
startDelayedUpdate();
|
||||
} // create new update request
|
||||
|
||||
// This middleware creates or destroys an interval that will cause workpad elements to update
|
||||
// clear any pending timeout
|
||||
stopAutoUpdate();
|
||||
|
||||
// if interval is larger than 0, start the delayed update
|
||||
if (shouldPlay) {
|
||||
startDelayedUpdate();
|
||||
} else {
|
||||
stopAutoUpdate();
|
||||
}
|
||||
};
|
||||
};
|
|
@ -4,18 +4,25 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Middleware } from 'redux';
|
||||
import { State } from '../../../types';
|
||||
// @ts-ignore Untyped Local
|
||||
import { fetchAllRenderables } from '../actions/elements';
|
||||
// @ts-ignore Untyped Local
|
||||
import { setRefreshInterval } from '../actions/workpad';
|
||||
import { inFlightComplete } from '../actions/resolved_args';
|
||||
import { getInFlight } from '../selectors/resolved_args';
|
||||
import { getRefreshInterval } from '../selectors/workpad';
|
||||
import { setRefreshInterval as setAppStateRefreshInterval } from '../../lib/app_state';
|
||||
import { createTimeInterval } from '../../lib/time_interval';
|
||||
|
||||
export const workpadRefresh = ({ dispatch, getState }) => next => {
|
||||
let refreshTimeout;
|
||||
export const workpadRefresh: Middleware<{}, State> = ({ dispatch, getState }) => next => {
|
||||
let refreshTimeout: number | undefined;
|
||||
let refreshInterval = 0;
|
||||
|
||||
function updateWorkpad() {
|
||||
cancelDelayedUpdate();
|
||||
|
||||
if (refreshInterval === 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -31,30 +38,43 @@ export const workpadRefresh = ({ dispatch, getState }) => next => {
|
|||
}
|
||||
}
|
||||
|
||||
function cancelDelayedUpdate() {
|
||||
clearTimeout(refreshTimeout);
|
||||
refreshTimeout = undefined;
|
||||
}
|
||||
|
||||
function startDelayedUpdate() {
|
||||
clearTimeout(refreshTimeout); // cancel any pending update requests
|
||||
refreshTimeout = setTimeout(() => {
|
||||
updateWorkpad();
|
||||
}, refreshInterval);
|
||||
if (!refreshTimeout) {
|
||||
clearTimeout(refreshTimeout); // cancel any pending update requests
|
||||
refreshTimeout = window.setTimeout(() => {
|
||||
updateWorkpad();
|
||||
}, refreshInterval);
|
||||
}
|
||||
}
|
||||
|
||||
return action => {
|
||||
const previousRefreshInterval = getRefreshInterval(getState());
|
||||
next(action);
|
||||
|
||||
refreshInterval = getRefreshInterval(getState());
|
||||
|
||||
// when in-flight requests are finished, update the workpad after a given delay
|
||||
if (action.type === inFlightComplete.toString() && refreshInterval > 0) {
|
||||
startDelayedUpdate();
|
||||
} // create new update request
|
||||
|
||||
// This middleware creates or destroys an interval that will cause workpad elements to update
|
||||
if (action.type === setRefreshInterval.toString()) {
|
||||
if (
|
||||
action.type === setRefreshInterval.toString() &&
|
||||
previousRefreshInterval !== refreshInterval
|
||||
) {
|
||||
// update the refresh interval
|
||||
refreshInterval = action.payload;
|
||||
|
||||
setAppStateRefreshInterval(createTimeInterval(refreshInterval));
|
||||
|
||||
// clear any pending timeout
|
||||
clearTimeout(refreshTimeout);
|
||||
cancelDelayedUpdate();
|
||||
|
||||
// if interval is larger than 0, start the delayed update
|
||||
if (refreshInterval > 0) {
|
Loading…
Add table
Add a link
Reference in a new issue