From 37e6c2746177a037d5a8ae58adca910f97f8c46b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 17 Apr 2020 15:51:18 +0200 Subject: [PATCH] Created mercure info reducer and loaded info when server is reachable --- src/container/index.js | 2 + src/mercure/reducers/mercureInfo.js | 41 ++++++++++++++++++++ src/mercure/services/provideServices.js | 8 ++++ src/reducers/index.js | 2 + src/servers/reducers/selectedServer.js | 5 ++- src/servers/services/provideServices.js | 2 +- src/utils/services/ShlinkApiClient.js | 2 + test/servers/reducers/selectedServer.test.js | 14 ++++--- test/utils/services/ShlinkApiClient.test.js | 16 ++++++++ 9 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 src/mercure/reducers/mercureInfo.js create mode 100644 src/mercure/services/provideServices.js diff --git a/src/container/index.js b/src/container/index.js index 771a46e8..d634dffb 100644 --- a/src/container/index.js +++ b/src/container/index.js @@ -9,6 +9,7 @@ import provideServersServices from '../servers/services/provideServices'; import provideVisitsServices from '../visits/services/provideServices'; import provideTagsServices from '../tags/services/provideServices'; import provideUtilsServices from '../utils/services/provideServices'; +import provideMercureServices from '../mercure/services/provideServices'; const bottle = new Bottle(); const { container } = bottle; @@ -34,5 +35,6 @@ provideServersServices(bottle, connect, withRouter); provideTagsServices(bottle, connect); provideVisitsServices(bottle, connect); provideUtilsServices(bottle); +provideMercureServices(bottle); export default container; diff --git a/src/mercure/reducers/mercureInfo.js b/src/mercure/reducers/mercureInfo.js new file mode 100644 index 00000000..aa75d26a --- /dev/null +++ b/src/mercure/reducers/mercureInfo.js @@ -0,0 +1,41 @@ +import { handleActions } from 'redux-actions'; +import PropTypes from 'prop-types'; + +/* eslint-disable padding-line-between-statements */ +export const GET_MERCURE_INFO_START = 'shlink/mercure/GET_MERCURE_INFO_START'; +export const GET_MERCURE_INFO_ERROR = 'shlink/mercure/GET_MERCURE_INFO_ERROR'; +export const GET_MERCURE_INFO = 'shlink/mercure/GET_MERCURE_INFO'; +/* eslint-enable padding-line-between-statements */ + +export const MercureInfoType = PropTypes.shape({ + token: PropTypes.string, + mercureHubUrl: PropTypes.string, + loading: PropTypes.bool, + error: PropTypes.bool, +}); + +const initialState = { + token: undefined, + mercureHubUrl: undefined, + loading: false, + error: false, +}; + +export default handleActions({ + [GET_MERCURE_INFO_START]: (state) => ({ ...state, loading: true, error: false }), + [GET_MERCURE_INFO_ERROR]: (state) => ({ ...state, loading: false, error: true }), + [GET_MERCURE_INFO]: (state, { token, mercureHubUrl }) => ({ token, mercureHubUrl, loading: false, error: false }), +}, initialState); + +export const loadMercureInfo = (buildShlinkApiClient) => () => async (dispatch, getState) => { + dispatch({ type: GET_MERCURE_INFO_START }); + const { mercureInfo } = buildShlinkApiClient(getState); + + try { + const result = await mercureInfo(); + + dispatch({ type: GET_MERCURE_INFO, ...result }); + } catch (e) { + dispatch({ type: GET_MERCURE_INFO_ERROR }); + } +}; diff --git a/src/mercure/services/provideServices.js b/src/mercure/services/provideServices.js new file mode 100644 index 00000000..152ebe4a --- /dev/null +++ b/src/mercure/services/provideServices.js @@ -0,0 +1,8 @@ +import { loadMercureInfo } from '../reducers/mercureInfo'; + +const provideServices = (bottle) => { + // Actions + bottle.serviceFactory('loadMercureInfo', loadMercureInfo, 'buildShlinkApiClient'); +}; + +export default provideServices; diff --git a/src/reducers/index.js b/src/reducers/index.js index 2d80d488..8b96ede8 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -13,6 +13,7 @@ import shortUrlDetailReducer from '../visits/reducers/shortUrlDetail'; import tagsListReducer from '../tags/reducers/tagsList'; import tagDeleteReducer from '../tags/reducers/tagDelete'; import tagEditReducer from '../tags/reducers/tagEdit'; +import mercureInfoReducer from '../mercure/reducers/mercureInfo'; export default combineReducers({ servers: serversReducer, @@ -29,4 +30,5 @@ export default combineReducers({ tagsList: tagsListReducer, tagDelete: tagDeleteReducer, tagEdit: tagEditReducer, + mercureInfo: mercureInfoReducer, }); diff --git a/src/servers/reducers/selectedServer.js b/src/servers/reducers/selectedServer.js index bd62181b..64d94ae1 100644 --- a/src/servers/reducers/selectedServer.js +++ b/src/servers/reducers/selectedServer.js @@ -25,7 +25,9 @@ const getServerVersion = memoizeWith(identity, (serverId, health) => health().th export const resetSelectedServer = createAction(RESET_SELECTED_SERVER); -export const selectServer = ({ findServerById }, buildShlinkApiClient) => (serverId) => async (dispatch) => { +export const selectServer = ({ findServerById }, buildShlinkApiClient, loadMercureInfo) => (serverId) => async ( + dispatch +) => { dispatch(resetSelectedServer()); dispatch(resetShortUrlParams()); const selectedServer = findServerById(serverId); @@ -51,6 +53,7 @@ export const selectServer = ({ findServerById }, buildShlinkApiClient) => (serve printableVersion, }, }); + dispatch(loadMercureInfo()); } catch (e) { dispatch({ type: SELECT_SERVER, diff --git a/src/servers/services/provideServices.js b/src/servers/services/provideServices.js index 0516f665..ce325ffd 100644 --- a/src/servers/services/provideServices.js +++ b/src/servers/services/provideServices.js @@ -47,7 +47,7 @@ const provideServices = (bottle, connect, withRouter) => { bottle.service('ServersExporter', ServersExporter, 'ServersService', 'window', 'csvjson'); // Actions - bottle.serviceFactory('selectServer', selectServer, 'ServersService', 'buildShlinkApiClient'); + bottle.serviceFactory('selectServer', selectServer, 'ServersService', 'buildShlinkApiClient', 'loadMercureInfo'); bottle.serviceFactory('createServer', createServer, 'ServersService', 'listServers'); bottle.serviceFactory('createServers', createServers, 'ServersService', 'listServers'); bottle.serviceFactory('deleteServer', deleteServer, 'ServersService', 'listServers'); diff --git a/src/utils/services/ShlinkApiClient.js b/src/utils/services/ShlinkApiClient.js index fc9d5652..8ba12ff8 100644 --- a/src/utils/services/ShlinkApiClient.js +++ b/src/utils/services/ShlinkApiClient.js @@ -66,6 +66,8 @@ export default class ShlinkApiClient { health = () => this._performRequest('/health', 'GET').then((resp) => resp.data); + mercureInfo = () => this._performRequest('/mercure-info', 'GET').then((resp) => resp.data); + _performRequest = async (url, method = 'GET', query = {}, body = {}) => { try { return await this.axios({ diff --git a/test/servers/reducers/selectedServer.test.js b/test/servers/reducers/selectedServer.test.js index e073f183..2efd8291 100644 --- a/test/servers/reducers/selectedServer.test.js +++ b/test/servers/reducers/selectedServer.test.js @@ -40,6 +40,7 @@ describe('selectedServerReducer', () => { }; const buildApiClient = jest.fn().mockReturnValue(apiClientMock); const dispatch = jest.fn(); + const loadMercureInfo = jest.fn(); afterEach(jest.clearAllMocks); @@ -56,16 +57,17 @@ describe('selectedServerReducer', () => { apiClientMock.health.mockResolvedValue({ version: serverVersion }); - await selectServer(ServersServiceMock, buildApiClient)(uuid())(dispatch); + await selectServer(ServersServiceMock, buildApiClient, loadMercureInfo)(uuid())(dispatch); - expect(dispatch).toHaveBeenCalledTimes(3); + expect(dispatch).toHaveBeenCalledTimes(4); expect(dispatch).toHaveBeenNthCalledWith(1, { type: RESET_SELECTED_SERVER }); expect(dispatch).toHaveBeenNthCalledWith(2, { type: RESET_SHORT_URL_PARAMS }); expect(dispatch).toHaveBeenNthCalledWith(3, { type: SELECT_SERVER, selectedServer: expectedSelectedServer }); + expect(loadMercureInfo).toHaveBeenCalledTimes(1); }); it('invokes dependencies', async () => { - await selectServer(ServersServiceMock, buildApiClient)(uuid())(() => {}); + await selectServer(ServersServiceMock, buildApiClient, loadMercureInfo)(uuid())(() => {}); expect(ServersServiceMock.findServerById).toHaveBeenCalledTimes(1); expect(buildApiClient).toHaveBeenCalledTimes(1); @@ -76,10 +78,11 @@ describe('selectedServerReducer', () => { apiClientMock.health.mockRejectedValue({}); - await selectServer(ServersServiceMock, buildApiClient)(uuid())(dispatch); + await selectServer(ServersServiceMock, buildApiClient, loadMercureInfo)(uuid())(dispatch); expect(apiClientMock.health).toHaveBeenCalled(); expect(dispatch).toHaveBeenNthCalledWith(3, { type: SELECT_SERVER, selectedServer: expectedSelectedServer }); + expect(loadMercureInfo).not.toHaveBeenCalled(); }); it('dispatches error when server is not found', async () => { @@ -87,11 +90,12 @@ describe('selectedServerReducer', () => { ServersServiceMock.findServerById.mockReturnValue(undefined); - await selectServer(ServersServiceMock, buildApiClient)(uuid())(dispatch); + await selectServer(ServersServiceMock, buildApiClient, loadMercureInfo)(uuid())(dispatch); expect(ServersServiceMock.findServerById).toHaveBeenCalled(); expect(apiClientMock.health).not.toHaveBeenCalled(); expect(dispatch).toHaveBeenNthCalledWith(3, { type: SELECT_SERVER, selectedServer: expectedSelectedServer }); + expect(loadMercureInfo).not.toHaveBeenCalled(); }); }); }); diff --git a/test/utils/services/ShlinkApiClient.test.js b/test/utils/services/ShlinkApiClient.test.js index 1f35d268..5b41516b 100644 --- a/test/utils/services/ShlinkApiClient.test.js +++ b/test/utils/services/ShlinkApiClient.test.js @@ -209,4 +209,20 @@ describe('ShlinkApiClient', () => { expect(result).toEqual(expectedData); }); }); + + describe('mercureInfo', () => { + it('returns mercure info', async () => { + const expectedData = { + token: 'abc.123.def', + mercureHubUrl: 'http://example.com/.well-known/mercure', + }; + const axiosSpy = jest.fn(createAxiosMock({ data: expectedData })); + const { mercureInfo } = new ShlinkApiClient(axiosSpy); + + const result = await mercureInfo(); + + expect(axiosSpy).toHaveBeenCalled(); + expect(result).toEqual(expectedData); + }); + }); });