diff --git a/CHANGELOG.md b/CHANGELOG.md index 54dedb03..3c99e2e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org). +## 2.2.1 - 2019-10-18 + +#### Added + +* *Nothing* + +#### Changed + +* *Nothing* + +#### Deprecated + +* *Nothing* + +#### Removed + +* *Nothing* + +#### Fixed + +* [#165](https://github.com/shlinkio/shlink-web-client/issues/165) Fixed error thrown when opening "create" page while using a Shlink version which does not return a valid SemVer version (like `latest` docker image, or any development instance). + + ## 2.2.0 - 2019-10-05 #### Added diff --git a/src/servers/reducers/selectedServer.js b/src/servers/reducers/selectedServer.js index 4b07b219..f683fbe7 100644 --- a/src/servers/reducers/selectedServer.js +++ b/src/servers/reducers/selectedServer.js @@ -1,9 +1,14 @@ import { createAction, handleActions } from 'redux-actions'; import { resetShortUrlParams } from '../../short-urls/reducers/shortUrlsListParams'; +import { versionIsValidSemVer } from '../../utils/utils'; /* eslint-disable padding-line-between-statements */ export const SELECT_SERVER = 'shlink/selectedServer/SELECT_SERVER'; export const RESET_SELECTED_SERVER = 'shlink/selectedServer/RESET_SELECTED_SERVER'; + +export const MIN_FALLBACK_VERSION = '1.0.0'; +export const MAX_FALLBACK_VERSION = '999.999.999'; +export const LATEST_VERSION_CONSTRAINT = 'latest'; /* eslint-enable padding-line-between-statements */ const initialState = null; @@ -15,7 +20,10 @@ export const selectServer = ({ findServerById }, buildShlinkApiClient) => (serve const selectedServer = findServerById(serverId); const { health } = await buildShlinkApiClient(selectedServer); - const { version } = await health().catch(() => ({ version: '1.0.0' })); + const version = await health() + .then(({ version }) => version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version) + .then((version) => !versionIsValidSemVer(version) ? MIN_FALLBACK_VERSION : version) + .catch(() => MIN_FALLBACK_VERSION); dispatch({ type: SELECT_SERVER, diff --git a/src/utils/utils.js b/src/utils/utils.js index 3edcb7fb..304116fe 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -60,3 +60,11 @@ export const compareVersions = (firstVersion, operator, secondVersion) => compar secondVersion, operator ); + +export const versionIsValidSemVer = (version) => { + try { + return compareVersions(version, '=', version); + } catch (e) { + return false; + } +}; diff --git a/test/servers/reducers/selectedServer.test.js b/test/servers/reducers/selectedServer.test.js index d843025f..93baad62 100644 --- a/test/servers/reducers/selectedServer.test.js +++ b/test/servers/reducers/selectedServer.test.js @@ -1,8 +1,11 @@ +import each from 'jest-each'; import reducer, { selectServer, resetSelectedServer, RESET_SELECTED_SERVER, SELECT_SERVER, + MAX_FALLBACK_VERSION, + MIN_FALLBACK_VERSION, } from '../../../src/servers/reducers/selectedServer'; import { RESET_SHORT_URL_PARAMS } from '../../../src/short-urls/reducers/shortUrlsListParams'; @@ -34,26 +37,25 @@ describe('selectedServerReducer', () => { findServerById: jest.fn(() => selectedServer), }; const apiClientMock = { - health: jest.fn().mockResolvedValue({ version }), + health: jest.fn(), }; const buildApiClient = jest.fn().mockResolvedValue(apiClientMock); + const dispatch = jest.fn(); - beforeEach(() => { - apiClientMock.health.mockClear(); - buildApiClient.mockClear(); - }); + afterEach(jest.clearAllMocks); - afterEach(() => { - ServersServiceMock.findServerById.mockClear(); - }); - - it('dispatches proper actions', async () => { - const dispatch = jest.fn(); + each([ + [ version, version ], + [ 'latest', MAX_FALLBACK_VERSION ], + [ '%invalid_semver%', MIN_FALLBACK_VERSION ], + ]).it('dispatches proper actions', async (serverVersion, expectedVersion) => { const expectedSelectedServer = { ...selectedServer, - version, + version: expectedVersion, }; + apiClientMock.health.mockResolvedValue({ version: serverVersion }); + await selectServer(ServersServiceMock, buildApiClient)(serverId)(dispatch); expect(dispatch).toHaveBeenCalledTimes(2); @@ -67,5 +69,18 @@ describe('selectedServerReducer', () => { expect(ServersServiceMock.findServerById).toHaveBeenCalledTimes(1); expect(buildApiClient).toHaveBeenCalledTimes(1); }); + + it('falls back to min version when health endpoint fails', async () => { + const expectedSelectedServer = { + ...selectedServer, + version: MIN_FALLBACK_VERSION, + }; + + apiClientMock.health.mockRejectedValue({}); + + await selectServer(ServersServiceMock, buildApiClient)(serverId)(dispatch); + + expect(dispatch).toHaveBeenNthCalledWith(2, { type: SELECT_SERVER, selectedServer: expectedSelectedServer }); + }); }); });