mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-10 02:07:26 +03:00
Merge pull request #122 from acelaya/feature/cancel-visits-load
Feature/cancel visits load
This commit is contained in:
commit
60576388c5
8 changed files with 53 additions and 11 deletions
|
@ -1,7 +1,7 @@
|
||||||
language: node_js
|
language: node_js
|
||||||
|
|
||||||
node_js:
|
node_js:
|
||||||
- "stable"
|
- "10.15.3"
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
yarn: true
|
yarn: true
|
||||||
|
|
|
@ -25,6 +25,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
#### Fixed
|
#### Fixed
|
||||||
|
|
||||||
* [#120](https://github.com/shlinkio/shlink-web-client/issues/120) Fixed crash when visits page is loaded and there are no visits with known cities.
|
* [#120](https://github.com/shlinkio/shlink-web-client/issues/120) Fixed crash when visits page is loaded and there are no visits with known cities.
|
||||||
|
* [#113](https://github.com/shlinkio/shlink-web-client/issues/113) Ensured visits loading is cancelled when the visits page is unmounted. Requests on flight will still finish.
|
||||||
|
|
||||||
|
|
||||||
## 2.0.2 - 2019-03-04
|
## 2.0.2 - 2019-03-04
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:10.15.2 as node
|
FROM node:10.15.3-alpine as node
|
||||||
COPY . /shlink-web-client
|
COPY . /shlink-web-client
|
||||||
RUN cd /shlink-web-client && yarn install && yarn build
|
RUN cd /shlink-web-client && yarn install && yarn build
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ const ShortUrlVisits = ({ processStatsFromVisits }) => class ShortUrlVisits exte
|
||||||
shortUrlVisits: shortUrlVisitsType,
|
shortUrlVisits: shortUrlVisitsType,
|
||||||
getShortUrlDetail: PropTypes.func,
|
getShortUrlDetail: PropTypes.func,
|
||||||
shortUrlDetail: shortUrlDetailType,
|
shortUrlDetail: shortUrlDetailType,
|
||||||
|
cancelGetShortUrlVisits: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = { startDate: undefined, endDate: undefined };
|
state = { startDate: undefined, endDate: undefined };
|
||||||
|
@ -49,6 +50,10 @@ const ShortUrlVisits = ({ processStatsFromVisits }) => class ShortUrlVisits exte
|
||||||
getShortUrlDetail(shortCode);
|
getShortUrlDetail(shortCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.props.cancelGetShortUrlVisits();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { shortUrlVisits, shortUrlDetail } = this.props;
|
const { shortUrlVisits, shortUrlDetail } = this.props;
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { flatten, range, splitEvery } from 'ramda';
|
import { flatten, prop, range, splitEvery } from 'ramda';
|
||||||
|
|
||||||
/* eslint-disable padding-line-between-statements, newline-after-var */
|
/* eslint-disable padding-line-between-statements, newline-after-var */
|
||||||
export const GET_SHORT_URL_VISITS_START = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_START';
|
export const GET_SHORT_URL_VISITS_START = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_START';
|
||||||
export const GET_SHORT_URL_VISITS_ERROR = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_ERROR';
|
export const GET_SHORT_URL_VISITS_ERROR = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_ERROR';
|
||||||
export const GET_SHORT_URL_VISITS = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS';
|
export const GET_SHORT_URL_VISITS = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS';
|
||||||
export const GET_SHORT_URL_VISITS_LARGE = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_LARGE';
|
export const GET_SHORT_URL_VISITS_LARGE = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_LARGE';
|
||||||
|
export const GET_SHORT_URL_VISITS_CANCEL = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_CANCEL';
|
||||||
/* eslint-enable padding-line-between-statements, newline-after-var */
|
/* eslint-enable padding-line-between-statements, newline-after-var */
|
||||||
|
|
||||||
export const shortUrlVisitsType = PropTypes.shape({
|
export const shortUrlVisitsType = PropTypes.shape({
|
||||||
|
@ -19,6 +20,7 @@ const initialState = {
|
||||||
loading: false,
|
loading: false,
|
||||||
loadingLarge: false,
|
loadingLarge: false,
|
||||||
error: false,
|
error: false,
|
||||||
|
cancelLoad: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function reducer(state = initialState, action) {
|
export default function reducer(state = initialState, action) {
|
||||||
|
@ -28,6 +30,7 @@ export default function reducer(state = initialState, action) {
|
||||||
...state,
|
...state,
|
||||||
loading: true,
|
loading: true,
|
||||||
loadingLarge: false,
|
loadingLarge: false,
|
||||||
|
cancelLoad: false,
|
||||||
};
|
};
|
||||||
case GET_SHORT_URL_VISITS_ERROR:
|
case GET_SHORT_URL_VISITS_ERROR:
|
||||||
return {
|
return {
|
||||||
|
@ -35,6 +38,7 @@ export default function reducer(state = initialState, action) {
|
||||||
loading: false,
|
loading: false,
|
||||||
loadingLarge: false,
|
loadingLarge: false,
|
||||||
error: true,
|
error: true,
|
||||||
|
cancelLoad: false,
|
||||||
};
|
};
|
||||||
case GET_SHORT_URL_VISITS:
|
case GET_SHORT_URL_VISITS:
|
||||||
return {
|
return {
|
||||||
|
@ -42,12 +46,18 @@ export default function reducer(state = initialState, action) {
|
||||||
loading: false,
|
loading: false,
|
||||||
loadingLarge: false,
|
loadingLarge: false,
|
||||||
error: false,
|
error: false,
|
||||||
|
cancelLoad: false,
|
||||||
};
|
};
|
||||||
case GET_SHORT_URL_VISITS_LARGE:
|
case GET_SHORT_URL_VISITS_LARGE:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
loadingLarge: true,
|
loadingLarge: true,
|
||||||
};
|
};
|
||||||
|
case GET_SHORT_URL_VISITS_CANCEL:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
cancelLoad: true,
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -83,6 +93,12 @@ export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, dates) =>
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadPagesBlocks = async (pagesBlocks, index = 0) => {
|
const loadPagesBlocks = async (pagesBlocks, index = 0) => {
|
||||||
|
const { shortUrlVisits: { cancelLoad } } = getState();
|
||||||
|
|
||||||
|
if (cancelLoad) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const data = await loadVisitsInParallel(pagesBlocks[index]);
|
const data = await loadVisitsInParallel(pagesBlocks[index]);
|
||||||
|
|
||||||
if (index < pagesBlocks.length - 1) {
|
if (index < pagesBlocks.length - 1) {
|
||||||
|
@ -93,11 +109,11 @@ export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, dates) =>
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadVisitsInParallel = (pages) =>
|
const loadVisitsInParallel = (pages) =>
|
||||||
Promise.all(pages.map(async (page) => {
|
Promise.all(pages.map(
|
||||||
const { data } = await getShortUrlVisits(shortCode, { ...dates, page, itemsPerPage });
|
(page) =>
|
||||||
|
getShortUrlVisits(shortCode, { ...dates, page, itemsPerPage })
|
||||||
return data;
|
.then(prop('data'))
|
||||||
})).then(flatten);
|
)).then(flatten);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const visits = await loadVisits();
|
const visits = await loadVisits();
|
||||||
|
@ -107,3 +123,5 @@ export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, dates) =>
|
||||||
dispatch({ type: GET_SHORT_URL_VISITS_ERROR });
|
dispatch({ type: GET_SHORT_URL_VISITS_ERROR });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const cancelGetShortUrlVisits = () => ({ type: GET_SHORT_URL_VISITS_CANCEL });
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import ShortUrlVisits from '../ShortUrlVisits';
|
import ShortUrlVisits from '../ShortUrlVisits';
|
||||||
import { getShortUrlVisits } from '../reducers/shortUrlVisits';
|
import { cancelGetShortUrlVisits, getShortUrlVisits } from '../reducers/shortUrlVisits';
|
||||||
import { getShortUrlDetail } from '../reducers/shortUrlDetail';
|
import { getShortUrlDetail } from '../reducers/shortUrlDetail';
|
||||||
import * as visitsParser from './VisitsParser';
|
import * as visitsParser from './VisitsParser';
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ const provideServices = (bottle, connect) => {
|
||||||
bottle.serviceFactory('ShortUrlVisits', ShortUrlVisits, 'VisitsParser');
|
bottle.serviceFactory('ShortUrlVisits', ShortUrlVisits, 'VisitsParser');
|
||||||
bottle.decorator('ShortUrlVisits', connect(
|
bottle.decorator('ShortUrlVisits', connect(
|
||||||
[ 'shortUrlVisits', 'shortUrlDetail' ],
|
[ 'shortUrlVisits', 'shortUrlDetail' ],
|
||||||
[ 'getShortUrlVisits', 'getShortUrlDetail' ]
|
[ 'getShortUrlVisits', 'getShortUrlDetail', 'cancelGetShortUrlVisits' ]
|
||||||
));
|
));
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
|
@ -17,6 +17,7 @@ const provideServices = (bottle, connect) => {
|
||||||
// Actions
|
// Actions
|
||||||
bottle.serviceFactory('getShortUrlVisits', getShortUrlVisits, 'buildShlinkApiClient');
|
bottle.serviceFactory('getShortUrlVisits', getShortUrlVisits, 'buildShlinkApiClient');
|
||||||
bottle.serviceFactory('getShortUrlDetail', getShortUrlDetail, 'buildShlinkApiClient');
|
bottle.serviceFactory('getShortUrlDetail', getShortUrlDetail, 'buildShlinkApiClient');
|
||||||
|
bottle.serviceFactory('cancelGetShortUrlVisits', () => cancelGetShortUrlVisits);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default provideServices;
|
export default provideServices;
|
||||||
|
|
|
@ -29,6 +29,7 @@ describe('<ShortUrlVisits />', () => {
|
||||||
match={match}
|
match={match}
|
||||||
shortUrlVisits={shortUrlVisits}
|
shortUrlVisits={shortUrlVisits}
|
||||||
shortUrlDetail={{}}
|
shortUrlDetail={{}}
|
||||||
|
cancelGetShortUrlVisits={identity}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import reducer, {
|
import reducer, {
|
||||||
getShortUrlVisits,
|
getShortUrlVisits,
|
||||||
|
cancelGetShortUrlVisits,
|
||||||
GET_SHORT_URL_VISITS_START,
|
GET_SHORT_URL_VISITS_START,
|
||||||
GET_SHORT_URL_VISITS_ERROR,
|
GET_SHORT_URL_VISITS_ERROR,
|
||||||
GET_SHORT_URL_VISITS,
|
GET_SHORT_URL_VISITS,
|
||||||
GET_SHORT_URL_VISITS_LARGE,
|
GET_SHORT_URL_VISITS_LARGE,
|
||||||
|
GET_SHORT_URL_VISITS_CANCEL,
|
||||||
} from '../../../src/visits/reducers/shortUrlVisits';
|
} from '../../../src/visits/reducers/shortUrlVisits';
|
||||||
|
|
||||||
describe('shortUrlVisitsReducer', () => {
|
describe('shortUrlVisitsReducer', () => {
|
||||||
|
@ -23,6 +25,13 @@ describe('shortUrlVisitsReducer', () => {
|
||||||
expect(loadingLarge).toEqual(true);
|
expect(loadingLarge).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns cancelLoad on GET_SHORT_URL_VISITS_CANCEL', () => {
|
||||||
|
const state = reducer({ cancelLoad: false }, { type: GET_SHORT_URL_VISITS_CANCEL });
|
||||||
|
const { cancelLoad } = state;
|
||||||
|
|
||||||
|
expect(cancelLoad).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('stops loading and returns error on GET_SHORT_URL_VISITS_ERROR', () => {
|
it('stops loading and returns error on GET_SHORT_URL_VISITS_ERROR', () => {
|
||||||
const state = reducer({ loading: true, error: false }, { type: GET_SHORT_URL_VISITS_ERROR });
|
const state = reducer({ loading: true, error: false }, { type: GET_SHORT_URL_VISITS_ERROR });
|
||||||
const { loading, error } = state;
|
const { loading, error } = state;
|
||||||
|
@ -58,7 +67,9 @@ describe('shortUrlVisitsReducer', () => {
|
||||||
getShortUrlVisits: typeof returned === 'function' ? sinon.fake(returned) : sinon.fake.returns(returned),
|
getShortUrlVisits: typeof returned === 'function' ? sinon.fake(returned) : sinon.fake.returns(returned),
|
||||||
});
|
});
|
||||||
const dispatchMock = sinon.spy();
|
const dispatchMock = sinon.spy();
|
||||||
const getState = () => ({});
|
const getState = () => ({
|
||||||
|
shortUrlVisits: { cancelVisits: false },
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => dispatchMock.resetHistory());
|
beforeEach(() => dispatchMock.resetHistory());
|
||||||
|
|
||||||
|
@ -126,4 +137,9 @@ describe('shortUrlVisitsReducer', () => {
|
||||||
expect(visits).toEqual([{}, {}, {}, {}, {}, {}]);
|
expect(visits).toEqual([{}, {}, {}, {}, {}, {}]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('cancelGetShortUrlVisits', () => {
|
||||||
|
it('just returns the action with proper type', () =>
|
||||||
|
expect(cancelGetShortUrlVisits()).toEqual({ type: GET_SHORT_URL_VISITS_CANCEL }));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue