mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 17:40:23 +03:00
Added support to dispatch all UI actions based on mercure bindings on a specific schedule instead of real time
This commit is contained in:
parent
9b45513684
commit
ad437f655e
10 changed files with 31 additions and 18 deletions
|
@ -13,13 +13,22 @@ export function boundToMercureHub<T = {}>(
|
||||||
WrappedComponent: FC<MercureBoundProps & T>,
|
WrappedComponent: FC<MercureBoundProps & T>,
|
||||||
getTopicForProps: (props: T) => string,
|
getTopicForProps: (props: T) => string,
|
||||||
) {
|
) {
|
||||||
|
const pendingUpdates = new Set<CreateVisit>();
|
||||||
|
|
||||||
return (props: MercureBoundProps & T) => {
|
return (props: MercureBoundProps & T) => {
|
||||||
const { createNewVisit, loadMercureInfo, mercureInfo } = props;
|
const { createNewVisit, loadMercureInfo, mercureInfo } = props;
|
||||||
|
const { interval } = mercureInfo;
|
||||||
|
|
||||||
useEffect(
|
useEffect(() => {
|
||||||
bindToMercureTopic(mercureInfo, getTopicForProps(props), createNewVisit, loadMercureInfo),
|
const onMessage = (visit: CreateVisit) => interval ? pendingUpdates.add(visit) : createNewVisit(visit);
|
||||||
[ mercureInfo ],
|
|
||||||
);
|
interval && setInterval(() => {
|
||||||
|
pendingUpdates.forEach(createNewVisit);
|
||||||
|
pendingUpdates.clear();
|
||||||
|
}, interval * 1000 * 60);
|
||||||
|
|
||||||
|
bindToMercureTopic(mercureInfo, getTopicForProps(props), onMessage, loadMercureInfo);
|
||||||
|
}, [ mercureInfo ]);
|
||||||
|
|
||||||
return <WrappedComponent {...props} />;
|
return <WrappedComponent {...props} />;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { EventSourcePolyfill as EventSource } from 'event-source-polyfill';
|
import { EventSourcePolyfill as EventSource } from 'event-source-polyfill';
|
||||||
import { MercureInfo } from '../reducers/mercureInfo';
|
import { MercureInfo } from '../reducers/mercureInfo';
|
||||||
|
|
||||||
export const bindToMercureTopic = <T>(mercureInfo: MercureInfo, topic: string, onMessage: (message: T) => void, onTokenExpired: Function) => () => { // eslint-disable-line max-len
|
export const bindToMercureTopic = <T>(mercureInfo: MercureInfo, topic: string, onMessage: (message: T) => void, onTokenExpired: Function) => { // eslint-disable-line max-len
|
||||||
const { mercureHubUrl, token, loading, error } = mercureInfo;
|
const { mercureHubUrl, token, loading, error } = mercureInfo;
|
||||||
|
|
||||||
if (loading || error || !mercureHubUrl) {
|
if (loading || error || !mercureHubUrl) {
|
||||||
|
|
|
@ -13,11 +13,12 @@ export const GET_MERCURE_INFO = 'shlink/mercure/GET_MERCURE_INFO';
|
||||||
export interface MercureInfo {
|
export interface MercureInfo {
|
||||||
token?: string;
|
token?: string;
|
||||||
mercureHubUrl?: string;
|
mercureHubUrl?: string;
|
||||||
|
interval?: number;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: boolean;
|
error: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GetMercureInfoAction = Action<string> & ShlinkMercureInfo;
|
export type GetMercureInfoAction = Action<string> & ShlinkMercureInfo & { interval?: number };
|
||||||
|
|
||||||
const initialState: MercureInfo = {
|
const initialState: MercureInfo = {
|
||||||
loading: true,
|
loading: true,
|
||||||
|
@ -27,7 +28,7 @@ const initialState: MercureInfo = {
|
||||||
export default buildReducer<MercureInfo, GetMercureInfoAction>({
|
export default buildReducer<MercureInfo, GetMercureInfoAction>({
|
||||||
[GET_MERCURE_INFO_START]: (state) => ({ ...state, loading: true, error: false }),
|
[GET_MERCURE_INFO_START]: (state) => ({ ...state, loading: true, error: false }),
|
||||||
[GET_MERCURE_INFO_ERROR]: (state) => ({ ...state, loading: false, error: true }),
|
[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);
|
}, initialState);
|
||||||
|
|
||||||
export const loadMercureInfo = (buildShlinkApiClient: ShlinkApiClientBuilder) =>
|
export const loadMercureInfo = (buildShlinkApiClient: ShlinkApiClientBuilder) =>
|
||||||
|
@ -44,9 +45,9 @@ export const loadMercureInfo = (buildShlinkApiClient: ShlinkApiClientBuilder) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await mercureInfo();
|
const info = await mercureInfo();
|
||||||
|
|
||||||
dispatch<GetMercureInfoAction>({ type: GET_MERCURE_INFO, ...result });
|
dispatch<GetMercureInfoAction>({ type: GET_MERCURE_INFO, interval: settings.realTimeUpdates.interval, ...info });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch({ type: GET_MERCURE_INFO_ERROR });
|
dispatch({ type: GET_MERCURE_INFO_ERROR });
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ const RealTimeUpdates = (
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
min={0}
|
min={0}
|
||||||
placeholder={realTimeUpdates.enabled ? 'Immediate' : ''}
|
placeholder="Immediate"
|
||||||
disabled={!realTimeUpdates.enabled}
|
disabled={!realTimeUpdates.enabled}
|
||||||
value={intervalValue(realTimeUpdates.interval)}
|
value={intervalValue(realTimeUpdates.interval)}
|
||||||
onChange={(e) => setRealTimeUpdatesInterval(Number(e.target.value))}
|
onChange={(e) => setRealTimeUpdatesInterval(Number(e.target.value))}
|
||||||
|
|
|
@ -13,7 +13,10 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
bottle.serviceFactory('RealTimeUpdates', () => RealTimeUpdates);
|
bottle.serviceFactory('RealTimeUpdates', () => RealTimeUpdates);
|
||||||
bottle.decorator('RealTimeUpdates', connect([ 'settings' ], [ 'setRealTimeUpdatesInterval' ]));
|
bottle.decorator(
|
||||||
|
'RealTimeUpdates',
|
||||||
|
connect([ 'settings' ], [ 'toggleRealTimeUpdates', 'setRealTimeUpdatesInterval' ]),
|
||||||
|
);
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
bottle.serviceFactory('toggleRealTimeUpdates', () => toggleRealTimeUpdates);
|
bottle.serviceFactory('toggleRealTimeUpdates', () => toggleRealTimeUpdates);
|
||||||
|
|
|
@ -20,7 +20,7 @@ describe('helpers', () => {
|
||||||
[ Mock.of<MercureInfo>({ loading: false, error: false, mercureHubUrl: undefined }) ],
|
[ Mock.of<MercureInfo>({ loading: false, error: false, mercureHubUrl: undefined }) ],
|
||||||
[ Mock.of<MercureInfo>({ loading: true, error: true, mercureHubUrl: undefined }) ],
|
[ Mock.of<MercureInfo>({ loading: true, error: true, mercureHubUrl: undefined }) ],
|
||||||
])('does not bind an EventSource when loading, error or no hub URL', (mercureInfo) => {
|
])('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(EventSource).not.toHaveBeenCalled();
|
||||||
expect(onMessage).not.toHaveBeenCalled();
|
expect(onMessage).not.toHaveBeenCalled();
|
||||||
|
@ -40,7 +40,7 @@ describe('helpers', () => {
|
||||||
error: false,
|
error: false,
|
||||||
mercureHubUrl,
|
mercureHubUrl,
|
||||||
token,
|
token,
|
||||||
}, topic, onMessage, onTokenExpired)();
|
}, topic, onMessage, onTokenExpired);
|
||||||
|
|
||||||
expect(EventSource).toHaveBeenCalledWith(hubUrl, {
|
expect(EventSource).toHaveBeenCalledWith(hubUrl, {
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
@ -36,11 +36,11 @@ describe('mercureInfoReducer', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns mercure info on GET_MERCURE_INFO', () => {
|
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,
|
...mercureInfo,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: false,
|
error: false,
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ describe('<TagsList />', () => {
|
||||||
wrapper = shallow(
|
wrapper = shallow(
|
||||||
<TagsListComp
|
<TagsListComp
|
||||||
{...Mock.all<TagsListProps>()}
|
{...Mock.all<TagsListProps>()}
|
||||||
{...Mock.all<MercureBoundProps>()}
|
{...Mock.of<MercureBoundProps>({ mercureInfo: {} })}
|
||||||
forceListTags={identity}
|
forceListTags={identity}
|
||||||
filterTags={filterTags}
|
filterTags={filterTags}
|
||||||
tagsList={Mock.of<TagsList>(tagsList)}
|
tagsList={Mock.of<TagsList>(tagsList)}
|
||||||
|
|
|
@ -26,7 +26,7 @@ describe('<ShortUrlVisits />', () => {
|
||||||
wrapper = shallow(
|
wrapper = shallow(
|
||||||
<ShortUrlVisits
|
<ShortUrlVisits
|
||||||
{...Mock.all<ShortUrlVisitsProps>()}
|
{...Mock.all<ShortUrlVisitsProps>()}
|
||||||
{...Mock.all<MercureBoundProps>()}
|
{...Mock.of<MercureBoundProps>({ mercureInfo: {} })}
|
||||||
getShortUrlDetail={identity}
|
getShortUrlDetail={identity}
|
||||||
getShortUrlVisits={getShortUrlVisitsMock}
|
getShortUrlVisits={getShortUrlVisitsMock}
|
||||||
match={match}
|
match={match}
|
||||||
|
|
|
@ -26,7 +26,7 @@ describe('<TagVisits />', () => {
|
||||||
wrapper = shallow(
|
wrapper = shallow(
|
||||||
<TagVisits
|
<TagVisits
|
||||||
{...Mock.all<TagVisitsProps>()}
|
{...Mock.all<TagVisitsProps>()}
|
||||||
{...Mock.all<MercureBoundProps>()}
|
{...Mock.of<MercureBoundProps>({ mercureInfo: {} })}
|
||||||
getTagVisits={getTagVisitsMock}
|
getTagVisits={getTagVisitsMock}
|
||||||
match={match}
|
match={match}
|
||||||
history={history}
|
history={history}
|
||||||
|
|
Loading…
Reference in a new issue