diff --git a/CHANGELOG.md b/CHANGELOG.md index a298931d..68e10e2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), #### Changed * [#87](https://github.com/shlinkio/shlink-web-client/issues/87) and [#89](https://github.com/shlinkio/shlink-web-client/issues/89) Updated all dependencies to latest major versions. +* [#96](https://github.com/shlinkio/shlink-web-client/issues/96) Updated visits page to load visits in multiple paginated requests of `5000` visits when used shlink server supports it. This will prevent shlink to hang when trying to load big amounts of visits. #### Deprecated diff --git a/src/utils/services/ShlinkApiClient.js b/src/utils/services/ShlinkApiClient.js index 72ddfca8..42d27512 100644 --- a/src/utils/services/ShlinkApiClient.js +++ b/src/utils/services/ShlinkApiClient.js @@ -22,9 +22,9 @@ export default class ShlinkApiClient { .then((resp) => resp.data); }; - getShortUrlVisits = (shortCode, dates) => - this._performRequest(`/short-urls/${shortCode}/visits`, 'GET', dates) - .then((resp) => resp.data.visits.data); + getShortUrlVisits = (shortCode, query) => + this._performRequest(`/short-urls/${shortCode}/visits`, 'GET', query) + .then((resp) => resp.data.visits); getShortUrl = (shortCode) => this._performRequest(`/short-urls/${shortCode}`, 'GET') diff --git a/src/visits/reducers/shortUrlVisits.js b/src/visits/reducers/shortUrlVisits.js index 8bdedc33..32a86f47 100644 --- a/src/visits/reducers/shortUrlVisits.js +++ b/src/visits/reducers/shortUrlVisits.js @@ -46,10 +46,23 @@ export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, dates) => dispatch({ type: GET_SHORT_URL_VISITS_START }); const { selectedServer } = getState(); - const shlinkApiClient = buildShlinkApiClient(selectedServer); + const { getShortUrlVisits } = buildShlinkApiClient(selectedServer); + const itemsPerPage = 5000; + const isLastPage = ({ currentPage, pagesCount }) => currentPage >= pagesCount; + + const loadVisits = async (page = 1) => { + const { pagination, data } = await getShortUrlVisits(shortCode, { ...dates, page, itemsPerPage }); + + // If pagination was not returned, then this is an older shlink version. Just return data + if (!pagination || isLastPage(pagination)) { + return data; + } + + return data.concat(await loadVisits(page + 1)); + }; try { - const visits = await shlinkApiClient.getShortUrlVisits(shortCode, dates); + const visits = await loadVisits(); dispatch({ visits, type: GET_SHORT_URL_VISITS }); } catch (e) { diff --git a/test/utils/services/ShlinkApiClient.test.js b/test/utils/services/ShlinkApiClient.test.js index 24f17858..f41d324b 100644 --- a/test/utils/services/ShlinkApiClient.test.js +++ b/test/utils/services/ShlinkApiClient.test.js @@ -64,7 +64,7 @@ describe('ShlinkApiClient', () => { const lastAxiosCall = last(axiosSpy.getCalls()); const axiosArgs = head(lastAxiosCall.args); - expect(expectedVisits).toEqual(actualVisits); + expect({ data: expectedVisits }).toEqual(actualVisits); expect(axiosArgs.url).toContain('/short-urls/abc123/visits'); expect(axiosArgs.method).toEqual('GET'); }); diff --git a/test/visits/reducers/shortUrlVisits.test.js b/test/visits/reducers/shortUrlVisits.test.js index 48c8dc9d..676490e7 100644 --- a/test/visits/reducers/shortUrlVisits.test.js +++ b/test/visits/reducers/shortUrlVisits.test.js @@ -47,7 +47,7 @@ describe('shortUrlVisitsReducer', () => { describe('getShortUrlVisits', () => { const buildApiClientMock = (returned) => ({ - getShortUrlVisits: sinon.fake.returns(returned), + getShortUrlVisits: typeof returned === 'function' ? sinon.fake(returned) : sinon.fake.returns(returned), }); const dispatchMock = sinon.spy(); const getState = () => ({}); @@ -74,7 +74,13 @@ describe('shortUrlVisitsReducer', () => { it('dispatches start and success when promise is resolved', async () => { const resolvedVisits = [{}, {}]; - const ShlinkApiClient = buildApiClientMock(Promise.resolve(resolvedVisits)); + const ShlinkApiClient = buildApiClientMock(Promise.resolve({ + data: resolvedVisits, + pagination: { + currentPage: 1, + pagesCount: 1, + }, + })); const expectedDispatchCalls = 2; await getShortUrlVisits(() => ShlinkApiClient)('abc123')(dispatchMock, getState); @@ -91,5 +97,25 @@ describe('shortUrlVisitsReducer', () => { expect(secondCallType).toEqual(GET_SHORT_URL_VISITS); expect(visits).toEqual(resolvedVisits); }); + + it('performs multiple API requests when response contains more pages', async () => { + const expectedRequests = 3; + const ShlinkApiClient = buildApiClientMock((shortCode, { page }) => + Promise.resolve({ + data: [{}, {}], + pagination: { + currentPage: page, + pagesCount: expectedRequests, + }, + })); + + await getShortUrlVisits(() => ShlinkApiClient)('abc123')(dispatchMock, getState); + + const [ secondCallArg ] = dispatchMock.getCall(1).args; + const { visits } = secondCallArg; + + expect(ShlinkApiClient.getShortUrlVisits.callCount).toEqual(expectedRequests); + expect(visits).toEqual([{}, {}, {}, {}, {}, {}]); + }); }); });