From ad437f655e88fb03b8d0e7d8ca3d65f09e0147bc Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 12 Sep 2020 08:52:03 +0200 Subject: [PATCH] Added support to dispatch all UI actions based on mercure bindings on a specific schedule instead of real time --- src/mercure/helpers/boundToMercureHub.tsx | 17 +++++++++++++---- src/mercure/helpers/index.ts | 2 +- src/mercure/reducers/mercureInfo.ts | 9 +++++---- src/settings/RealTimeUpdates.tsx | 2 +- src/settings/services/provideServices.ts | 5 ++++- test/mercure/helpers/index.test.tsx | 4 ++-- test/mercure/reducers/mercureInfo.test.ts | 4 ++-- test/tags/TagsList.test.tsx | 2 +- test/visits/ShortUrlVisits.test.tsx | 2 +- test/visits/TagVisits.test.tsx | 2 +- 10 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/mercure/helpers/boundToMercureHub.tsx b/src/mercure/helpers/boundToMercureHub.tsx index e4f7ae71..b71fa421 100644 --- a/src/mercure/helpers/boundToMercureHub.tsx +++ b/src/mercure/helpers/boundToMercureHub.tsx @@ -13,13 +13,22 @@ export function boundToMercureHub( WrappedComponent: FC, getTopicForProps: (props: T) => string, ) { + const pendingUpdates = new Set(); + return (props: MercureBoundProps & T) => { const { createNewVisit, loadMercureInfo, mercureInfo } = props; + const { interval } = mercureInfo; - useEffect( - bindToMercureTopic(mercureInfo, getTopicForProps(props), createNewVisit, loadMercureInfo), - [ mercureInfo ], - ); + useEffect(() => { + const onMessage = (visit: CreateVisit) => interval ? pendingUpdates.add(visit) : createNewVisit(visit); + + interval && setInterval(() => { + pendingUpdates.forEach(createNewVisit); + pendingUpdates.clear(); + }, interval * 1000 * 60); + + bindToMercureTopic(mercureInfo, getTopicForProps(props), onMessage, loadMercureInfo); + }, [ mercureInfo ]); return ; }; diff --git a/src/mercure/helpers/index.ts b/src/mercure/helpers/index.ts index 997ecf07..19c2176c 100644 --- a/src/mercure/helpers/index.ts +++ b/src/mercure/helpers/index.ts @@ -1,7 +1,7 @@ import { EventSourcePolyfill as EventSource } from 'event-source-polyfill'; import { MercureInfo } from '../reducers/mercureInfo'; -export const bindToMercureTopic = (mercureInfo: MercureInfo, topic: string, onMessage: (message: T) => void, onTokenExpired: Function) => () => { // eslint-disable-line max-len +export const bindToMercureTopic = (mercureInfo: MercureInfo, topic: string, onMessage: (message: T) => void, onTokenExpired: Function) => { // eslint-disable-line max-len const { mercureHubUrl, token, loading, error } = mercureInfo; if (loading || error || !mercureHubUrl) { diff --git a/src/mercure/reducers/mercureInfo.ts b/src/mercure/reducers/mercureInfo.ts index 5b08c364..36017c73 100644 --- a/src/mercure/reducers/mercureInfo.ts +++ b/src/mercure/reducers/mercureInfo.ts @@ -13,11 +13,12 @@ export const GET_MERCURE_INFO = 'shlink/mercure/GET_MERCURE_INFO'; export interface MercureInfo { token?: string; mercureHubUrl?: string; + interval?: number; loading: boolean; error: boolean; } -export type GetMercureInfoAction = Action & ShlinkMercureInfo; +export type GetMercureInfoAction = Action & ShlinkMercureInfo & { interval?: number }; const initialState: MercureInfo = { loading: true, @@ -27,7 +28,7 @@ const initialState: MercureInfo = { export default buildReducer({ [GET_MERCURE_INFO_START]: (state) => ({ ...state, loading: true, error: false }), [GET_MERCURE_INFO_ERROR]: (state) => ({ ...state, loading: false, error: true }), - [GET_MERCURE_INFO]: (_, { token, mercureHubUrl }) => ({ token, mercureHubUrl, loading: false, error: false }), + [GET_MERCURE_INFO]: (_, action) => ({ ...action, loading: false, error: false }), }, initialState); export const loadMercureInfo = (buildShlinkApiClient: ShlinkApiClientBuilder) => @@ -44,9 +45,9 @@ export const loadMercureInfo = (buildShlinkApiClient: ShlinkApiClientBuilder) => } try { - const result = await mercureInfo(); + const info = await mercureInfo(); - dispatch({ type: GET_MERCURE_INFO, ...result }); + dispatch({ type: GET_MERCURE_INFO, interval: settings.realTimeUpdates.interval, ...info }); } catch (e) { dispatch({ type: GET_MERCURE_INFO_ERROR }); } diff --git a/src/settings/RealTimeUpdates.tsx b/src/settings/RealTimeUpdates.tsx index d89f6526..2e8b18b9 100644 --- a/src/settings/RealTimeUpdates.tsx +++ b/src/settings/RealTimeUpdates.tsx @@ -30,7 +30,7 @@ const RealTimeUpdates = ( setRealTimeUpdatesInterval(Number(e.target.value))} diff --git a/src/settings/services/provideServices.ts b/src/settings/services/provideServices.ts index 78d86e47..5da9eca1 100644 --- a/src/settings/services/provideServices.ts +++ b/src/settings/services/provideServices.ts @@ -13,7 +13,10 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Services bottle.serviceFactory('RealTimeUpdates', () => RealTimeUpdates); - bottle.decorator('RealTimeUpdates', connect([ 'settings' ], [ 'setRealTimeUpdatesInterval' ])); + bottle.decorator( + 'RealTimeUpdates', + connect([ 'settings' ], [ 'toggleRealTimeUpdates', 'setRealTimeUpdatesInterval' ]), + ); // Actions bottle.serviceFactory('toggleRealTimeUpdates', () => toggleRealTimeUpdates); diff --git a/test/mercure/helpers/index.test.tsx b/test/mercure/helpers/index.test.tsx index 3a7f897f..2fb971b4 100644 --- a/test/mercure/helpers/index.test.tsx +++ b/test/mercure/helpers/index.test.tsx @@ -20,7 +20,7 @@ describe('helpers', () => { [ Mock.of({ loading: false, error: false, mercureHubUrl: undefined }) ], [ Mock.of({ loading: true, error: true, mercureHubUrl: undefined }) ], ])('does not bind an EventSource when loading, error or no hub URL', (mercureInfo) => { - bindToMercureTopic(mercureInfo, '', identity, identity)(); + bindToMercureTopic(mercureInfo, '', identity, identity); expect(EventSource).not.toHaveBeenCalled(); expect(onMessage).not.toHaveBeenCalled(); @@ -40,7 +40,7 @@ describe('helpers', () => { error: false, mercureHubUrl, token, - }, topic, onMessage, onTokenExpired)(); + }, topic, onMessage, onTokenExpired); expect(EventSource).toHaveBeenCalledWith(hubUrl, { headers: { diff --git a/test/mercure/reducers/mercureInfo.test.ts b/test/mercure/reducers/mercureInfo.test.ts index 71954823..50d7ba15 100644 --- a/test/mercure/reducers/mercureInfo.test.ts +++ b/test/mercure/reducers/mercureInfo.test.ts @@ -36,11 +36,11 @@ describe('mercureInfoReducer', () => { }); it('returns mercure info on GET_MERCURE_INFO', () => { - expect(reducer(undefined, { type: GET_MERCURE_INFO, ...mercureInfo })).toEqual({ + expect(reducer(undefined, { type: GET_MERCURE_INFO, ...mercureInfo })).toEqual(expect.objectContaining({ ...mercureInfo, loading: false, error: false, - }); + })); }); }); diff --git a/test/tags/TagsList.test.tsx b/test/tags/TagsList.test.tsx index b72f5dc1..e6191451 100644 --- a/test/tags/TagsList.test.tsx +++ b/test/tags/TagsList.test.tsx @@ -19,7 +19,7 @@ describe('', () => { wrapper = shallow( ()} - {...Mock.all()} + {...Mock.of({ mercureInfo: {} })} forceListTags={identity} filterTags={filterTags} tagsList={Mock.of(tagsList)} diff --git a/test/visits/ShortUrlVisits.test.tsx b/test/visits/ShortUrlVisits.test.tsx index 8a980daa..7cb25272 100644 --- a/test/visits/ShortUrlVisits.test.tsx +++ b/test/visits/ShortUrlVisits.test.tsx @@ -26,7 +26,7 @@ describe('', () => { wrapper = shallow( ()} - {...Mock.all()} + {...Mock.of({ mercureInfo: {} })} getShortUrlDetail={identity} getShortUrlVisits={getShortUrlVisitsMock} match={match} diff --git a/test/visits/TagVisits.test.tsx b/test/visits/TagVisits.test.tsx index 5cc80f03..5d87c698 100644 --- a/test/visits/TagVisits.test.tsx +++ b/test/visits/TagVisits.test.tsx @@ -26,7 +26,7 @@ describe('', () => { wrapper = shallow( ()} - {...Mock.all()} + {...Mock.of({ mercureInfo: {} })} getTagVisits={getTagVisitsMock} match={match} history={history}