diff --git a/src/common/MenuLayout.js b/src/common/MenuLayout.js
index 5d42fda4..ce6e7eca 100644
--- a/src/common/MenuLayout.js
+++ b/src/common/MenuLayout.js
@@ -17,7 +17,16 @@ const propTypes = {
selectedServer: serverType,
};
-const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisits, ShlinkVersions, ServerError) => {
+const MenuLayout = (
+ TagsList,
+ ShortUrls,
+ AsideMenu,
+ CreateShortUrl,
+ ShortUrlVisits,
+ TagVisits,
+ ShlinkVersions,
+ ServerError
+) => {
const MenuLayoutComp = ({ match, location, selectedServer }) => {
const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle();
const { params: { serverId } } = match;
@@ -61,7 +70,7 @@ const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisi
- {/* */}
+
List short URLs}
diff --git a/src/common/services/provideServices.js b/src/common/services/provideServices.js
index a8c842b6..1d4b288b 100644
--- a/src/common/services/provideServices.js
+++ b/src/common/services/provideServices.js
@@ -27,6 +27,7 @@ const provideServices = (bottle, connect, withRouter) => {
'AsideMenu',
'CreateShortUrl',
'ShortUrlVisits',
+ 'TagVisits',
'ShlinkVersions',
'ServerError'
);
diff --git a/src/reducers/index.js b/src/reducers/index.js
index 51b95d8c..1cdf34fd 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -9,6 +9,7 @@ import shortUrlTagsReducer from '../short-urls/reducers/shortUrlTags';
import shortUrlMetaReducer from '../short-urls/reducers/shortUrlMeta';
import shortUrlEditionReducer from '../short-urls/reducers/shortUrlEdition';
import shortUrlVisitsReducer from '../visits/reducers/shortUrlVisits';
+import tagVisitsReducer from '../visits/reducers/tagVisits';
import shortUrlDetailReducer from '../visits/reducers/shortUrlDetail';
import tagsListReducer from '../tags/reducers/tagsList';
import tagDeleteReducer from '../tags/reducers/tagDelete';
@@ -27,6 +28,7 @@ export default combineReducers({
shortUrlMeta: shortUrlMetaReducer,
shortUrlEdition: shortUrlEditionReducer,
shortUrlVisits: shortUrlVisitsReducer,
+ tagVisits: tagVisitsReducer,
shortUrlDetail: shortUrlDetailReducer,
tagsList: tagsListReducer,
tagDelete: tagDeleteReducer,
diff --git a/src/short-urls/reducers/shortUrlsList.js b/src/short-urls/reducers/shortUrlsList.js
index b7d346cc..9953e4e2 100644
--- a/src/short-urls/reducers/shortUrlsList.js
+++ b/src/short-urls/reducers/shortUrlsList.js
@@ -1,8 +1,8 @@
import { handleActions } from 'redux-actions';
import { assoc, assocPath, reject } from 'ramda';
import PropTypes from 'prop-types';
-import { CREATE_SHORT_URL_VISIT } from '../../visits/reducers/shortUrlVisits';
import { shortUrlMatches } from '../helpers';
+import { CREATE_VISIT } from '../../visits/reducers/visitCreation';
import { SHORT_URL_TAGS_EDITED } from './shortUrlTags';
import { SHORT_URL_DELETED } from './shortUrlDeletion';
import { SHORT_URL_META_EDITED, shortUrlMetaType } from './shortUrlMeta';
@@ -50,7 +50,7 @@ export default handleActions({
[SHORT_URL_TAGS_EDITED]: setPropFromActionOnMatchingShortUrl('tags'),
[SHORT_URL_META_EDITED]: setPropFromActionOnMatchingShortUrl('meta'),
[SHORT_URL_EDITED]: setPropFromActionOnMatchingShortUrl('longUrl'),
- [CREATE_SHORT_URL_VISIT]: (state, { shortUrl: { shortCode, domain, visitsCount } }) => assocPath(
+ [CREATE_VISIT]: (state, { shortUrl: { shortCode, domain, visitsCount } }) => assocPath(
[ 'shortUrls', 'data' ],
state.shortUrls && state.shortUrls.data && state.shortUrls.data.map(
(shortUrl) => shortUrlMatches(shortUrl, shortCode, domain)
diff --git a/src/utils/services/ShlinkApiClient.js b/src/utils/services/ShlinkApiClient.js
index 76d1cd56..05a9c5bf 100644
--- a/src/utils/services/ShlinkApiClient.js
+++ b/src/utils/services/ShlinkApiClient.js
@@ -36,6 +36,10 @@ export default class ShlinkApiClient {
this._performRequest(`/short-urls/${shortCode}/visits`, 'GET', query)
.then((resp) => resp.data.visits);
+ getTagVisits = (tag, query) =>
+ this._performRequest(`/tags/${tag}/visits`, 'GET', query)
+ .then((resp) => resp.data.visits);
+
getShortUrl = (shortCode, domain) =>
this._performRequest(`/short-urls/${shortCode}`, 'GET', { domain })
.then((resp) => resp.data);
diff --git a/src/visits/TagVisits.js b/src/visits/TagVisits.js
new file mode 100644
index 00000000..5d9c32ef
--- /dev/null
+++ b/src/visits/TagVisits.js
@@ -0,0 +1,65 @@
+import React, { useEffect } from 'react';
+import PropTypes from 'prop-types';
+import { MercureInfoType } from '../mercure/reducers/mercureInfo';
+import { SettingsType } from '../settings/reducers/settings';
+import { bindToMercureTopic } from '../mercure/helpers';
+import { TagVisitsType } from './reducers/tagVisits';
+
+const propTypes = {
+ history: PropTypes.shape({
+ goBack: PropTypes.func,
+ }),
+ match: PropTypes.shape({
+ params: PropTypes.object,
+ }),
+ getTagVisits: PropTypes.func,
+ tagVisits: TagVisitsType,
+ cancelGetTagVisits: PropTypes.func,
+ createNewVisit: PropTypes.func,
+ loadMercureInfo: PropTypes.func,
+ mercureInfo: MercureInfoType,
+ settings: SettingsType,
+};
+
+const TagVisits = (VisitsStats) => {
+ const TagVisitsComp = ({
+ history,
+ match,
+ getTagVisits,
+ tagVisits,
+ cancelGetTagVisits,
+ createNewVisit,
+ loadMercureInfo,
+ mercureInfo,
+ settings: { realTimeUpdates },
+ }) => {
+ const { params } = match;
+ const { tag } = params;
+ const loadVisits = (dates) => getTagVisits(tag, dates);
+
+ console.log(history);
+
+ useEffect(
+ bindToMercureTopic(
+ mercureInfo,
+ realTimeUpdates,
+ 'https://shlink.io/new-visit',
+ createNewVisit,
+ loadMercureInfo
+ ),
+ [ mercureInfo ],
+ );
+
+ return (
+
+ {tag} - {tagVisits.visits.length}
+
+ );
+ };
+
+ TagVisitsComp.propTypes = propTypes;
+
+ return TagVisitsComp;
+};
+
+export default TagVisits;
diff --git a/src/visits/VisitsStats.js b/src/visits/VisitsStats.js
index 50cd2d2c..6747926d 100644
--- a/src/visits/VisitsStats.js
+++ b/src/visits/VisitsStats.js
@@ -17,7 +17,7 @@ import { VisitsInfoType } from './types';
const propTypes = {
children: PropTypes.node,
getVisits: PropTypes.func,
- visitsInfo: VisitsInfoType, // TODO VisitsInfo type
+ visitsInfo: VisitsInfoType,
cancelGetVisits: PropTypes.func,
matchMedia: PropTypes.func,
};
diff --git a/src/visits/reducers/common.js b/src/visits/reducers/common.js
new file mode 100644
index 00000000..022b4283
--- /dev/null
+++ b/src/visits/reducers/common.js
@@ -0,0 +1,56 @@
+import { flatten, prop, range, splitEvery } from 'ramda';
+
+const ITEMS_PER_PAGE = 5000;
+const isLastPage = ({ currentPage, pagesCount }) => currentPage >= pagesCount;
+
+export const getVisitsWithLoader = async (visitsLoader, extraFinishActionData, actionMap, dispatch, getState) => {
+ dispatch({ type: actionMap.start });
+
+ const loadVisits = async (page = 1) => {
+ const { pagination, data } = await visitsLoader(page, ITEMS_PER_PAGE);
+
+ // If pagination was not returned, then this is an old shlink version. Just return data
+ if (!pagination || isLastPage(pagination)) {
+ return data;
+ }
+
+ // If there are more pages, make requests in blocks of 4
+ const parallelRequestsCount = 4;
+ const parallelStartingPage = 2;
+ const pagesRange = range(parallelStartingPage, pagination.pagesCount + 1);
+ const pagesBlocks = splitEvery(parallelRequestsCount, pagesRange);
+
+ if (pagination.pagesCount - 1 > parallelRequestsCount) {
+ dispatch({ type: actionMap.large });
+ }
+
+ return data.concat(await loadPagesBlocks(pagesBlocks));
+ };
+
+ const loadPagesBlocks = async (pagesBlocks, index = 0) => {
+ const { shortUrlVisits: { cancelLoad } } = getState();
+
+ if (cancelLoad) {
+ return [];
+ }
+
+ const data = await loadVisitsInParallel(pagesBlocks[index]);
+
+ if (index < pagesBlocks.length - 1) {
+ return data.concat(await loadPagesBlocks(pagesBlocks, index + 1));
+ }
+
+ return data;
+ };
+
+ const loadVisitsInParallel = (pages) =>
+ Promise.all(pages.map((page) => visitsLoader(page, ITEMS_PER_PAGE).then(prop('data')))).then(flatten);
+
+ try {
+ const visits = await loadVisits();
+
+ dispatch({ ...extraFinishActionData, visits, type: actionMap.finish });
+ } catch (e) {
+ dispatch({ type: actionMap.error });
+ }
+};
diff --git a/src/visits/reducers/shortUrlVisits.js b/src/visits/reducers/shortUrlVisits.js
index a52a4587..58b5d49c 100644
--- a/src/visits/reducers/shortUrlVisits.js
+++ b/src/visits/reducers/shortUrlVisits.js
@@ -1,8 +1,9 @@
import { createAction, handleActions } from 'redux-actions';
import PropTypes from 'prop-types';
-import { flatten, prop, range, splitEvery } from 'ramda';
import { shortUrlMatches } from '../../short-urls/helpers';
import { VisitType } from '../types';
+import { getVisitsWithLoader } from './common';
+import { CREATE_VISIT } from './visitCreation';
/* eslint-disable padding-line-between-statements */
export const GET_SHORT_URL_VISITS_START = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_START';
@@ -10,7 +11,6 @@ export const GET_SHORT_URL_VISITS_ERROR = 'shlink/shortUrlVisits/GET_SHORT_URL_V
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_CANCEL = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_CANCEL';
-export const CREATE_SHORT_URL_VISIT = 'shlink/shortUrlVisits/CREATE_SHORT_URL_VISIT';
/* eslint-enable padding-line-between-statements */
export const shortUrlVisitsType = PropTypes.shape({ // TODO Should extend from VisitInfoType
@@ -18,6 +18,7 @@ export const shortUrlVisitsType = PropTypes.shape({ // TODO Should extend from V
shortCode: PropTypes.string,
domain: PropTypes.string,
loading: PropTypes.bool,
+ loadingLarge: PropTypes.bool,
error: PropTypes.bool,
});
@@ -56,7 +57,7 @@ export default handleActions({
}),
[GET_SHORT_URL_VISITS_LARGE]: (state) => ({ ...state, loadingLarge: true }),
[GET_SHORT_URL_VISITS_CANCEL]: (state) => ({ ...state, cancelLoad: true }),
- [CREATE_SHORT_URL_VISIT]: (state, { shortUrl, visit }) => { // eslint-disable-line object-shorthand
+ [CREATE_VISIT]: (state, { shortUrl, visit }) => { // eslint-disable-line object-shorthand
const { shortCode, domain, visits } = state;
if (!shortUrlMatches(shortUrl, shortCode, domain)) {
@@ -67,65 +68,18 @@ export default handleActions({
},
}, initialState);
-export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, query = {}) => async (dispatch, getState) => {
- dispatch({ type: GET_SHORT_URL_VISITS_START });
+export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, query = {}) => (dispatch, getState) => {
const { getShortUrlVisits } = buildShlinkApiClient(getState);
- const itemsPerPage = 5000;
- const isLastPage = ({ currentPage, pagesCount }) => currentPage >= pagesCount;
-
- const loadVisits = async (page = 1) => {
- const { pagination, data } = await getShortUrlVisits(shortCode, { ...query, page, itemsPerPage });
-
- // If pagination was not returned, then this is an older shlink version. Just return data
- if (!pagination || isLastPage(pagination)) {
- return data;
- }
-
- // If there are more pages, make requests in blocks of 4
- const parallelRequestsCount = 4;
- const parallelStartingPage = 2;
- const pagesRange = range(parallelStartingPage, pagination.pagesCount + 1);
- const pagesBlocks = splitEvery(parallelRequestsCount, pagesRange);
-
- if (pagination.pagesCount - 1 > parallelRequestsCount) {
- dispatch({ type: GET_SHORT_URL_VISITS_LARGE });
- }
-
- return data.concat(await loadPagesBlocks(pagesBlocks));
+ const visitsLoader = (page, itemsPerPage) => getShortUrlVisits(shortCode, { ...query, page, itemsPerPage });
+ const extraFinishActionData = { shortCode, domain: query.domain };
+ const actionMap = {
+ start: GET_SHORT_URL_VISITS_START,
+ large: GET_SHORT_URL_VISITS_LARGE,
+ finish: GET_SHORT_URL_VISITS,
+ error: GET_SHORT_URL_VISITS_ERROR,
};
- const loadPagesBlocks = async (pagesBlocks, index = 0) => {
- const { shortUrlVisits: { cancelLoad } } = getState();
-
- if (cancelLoad) {
- return [];
- }
-
- const data = await loadVisitsInParallel(pagesBlocks[index]);
-
- if (index < pagesBlocks.length - 1) {
- return data.concat(await loadPagesBlocks(pagesBlocks, index + 1));
- }
-
- return data;
- };
-
- const loadVisitsInParallel = (pages) =>
- Promise.all(pages.map(
- (page) =>
- getShortUrlVisits(shortCode, { ...query, page, itemsPerPage })
- .then(prop('data'))
- )).then(flatten);
-
- try {
- const visits = await loadVisits();
-
- dispatch({ visits, shortCode, domain: query.domain, type: GET_SHORT_URL_VISITS });
- } catch (e) {
- dispatch({ type: GET_SHORT_URL_VISITS_ERROR });
- }
+ return getVisitsWithLoader(visitsLoader, extraFinishActionData, actionMap, dispatch, getState);
};
export const cancelGetShortUrlVisits = createAction(GET_SHORT_URL_VISITS_CANCEL);
-
-export const createNewVisit = ({ shortUrl, visit }) => ({ shortUrl, visit, type: CREATE_SHORT_URL_VISIT });
diff --git a/src/visits/reducers/tagVisits.js b/src/visits/reducers/tagVisits.js
new file mode 100644
index 00000000..c59facf2
--- /dev/null
+++ b/src/visits/reducers/tagVisits.js
@@ -0,0 +1,81 @@
+import { createAction, handleActions } from 'redux-actions';
+import PropTypes from 'prop-types';
+import { VisitType } from '../types';
+import { getVisitsWithLoader } from './common';
+import { CREATE_VISIT } from './visitCreation';
+
+/* eslint-disable padding-line-between-statements */
+export const GET_TAG_VISITS_START = 'shlink/tagVisits/GET_TAG_VISITS_START';
+export const GET_TAG_VISITS_ERROR = 'shlink/tagVisits/GET_TAG_VISITS_ERROR';
+export const GET_TAG_VISITS = 'shlink/tagVisits/GET_TAG_VISITS';
+export const GET_TAG_VISITS_LARGE = 'shlink/tagVisits/GET_TAG_VISITS_LARGE';
+export const GET_TAG_VISITS_CANCEL = 'shlink/tagVisits/GET_TAG_VISITS_CANCEL';
+/* eslint-enable padding-line-between-statements */
+
+export const TagVisitsType = PropTypes.shape({ // TODO Should extend from VisitInfoType
+ visits: PropTypes.arrayOf(VisitType),
+ tag: PropTypes.string,
+ loading: PropTypes.bool,
+ loadingLarge: PropTypes.bool,
+ error: PropTypes.bool,
+});
+
+const initialState = {
+ visits: [],
+ tag: '',
+ loading: false,
+ loadingLarge: false,
+ error: false,
+ cancelLoad: false,
+};
+
+export default handleActions({
+ [GET_TAG_VISITS_START]: (state) => ({
+ ...state,
+ loading: true,
+ loadingLarge: false,
+ cancelLoad: false,
+ }),
+ [GET_TAG_VISITS_ERROR]: (state) => ({
+ ...state,
+ loading: false,
+ loadingLarge: false,
+ error: true,
+ cancelLoad: false,
+ }),
+ [GET_TAG_VISITS]: (state, { visits, tag }) => ({
+ visits,
+ tag,
+ loading: false,
+ loadingLarge: false,
+ error: false,
+ cancelLoad: false,
+ }),
+ [GET_TAG_VISITS_LARGE]: (state) => ({ ...state, loadingLarge: true }),
+ [GET_TAG_VISITS_CANCEL]: (state) => ({ ...state, cancelLoad: true }),
+ [CREATE_VISIT]: (state, { shortUrl, visit }) => { // eslint-disable-line object-shorthand
+ const { tag, visits } = state;
+
+ if (!shortUrl.tags.includes(tag)) {
+ return state;
+ }
+
+ return { ...state, visits: [ ...visits, visit ] };
+ },
+}, initialState);
+
+export const getTagVisits = (buildShlinkApiClient) => (tag, query = {}) => (dispatch, getState) => {
+ const { getTagVisits } = buildShlinkApiClient(getState);
+ const visitsLoader = (page, itemsPerPage) => getTagVisits(tag, { ...query, page, itemsPerPage });
+ const extraFinishActionData = { tag };
+ const actionMap = {
+ start: GET_TAG_VISITS_START,
+ large: GET_TAG_VISITS_LARGE,
+ finish: GET_TAG_VISITS,
+ error: GET_TAG_VISITS_ERROR,
+ };
+
+ return getVisitsWithLoader(visitsLoader, extraFinishActionData, actionMap, dispatch, getState);
+};
+
+export const cancelGetTagVisits = createAction(GET_TAG_VISITS_CANCEL);
diff --git a/src/visits/reducers/visitCreation.js b/src/visits/reducers/visitCreation.js
new file mode 100644
index 00000000..e37890a1
--- /dev/null
+++ b/src/visits/reducers/visitCreation.js
@@ -0,0 +1,3 @@
+export const CREATE_VISIT = 'shlink/visitCreation/CREATE_VISIT';
+
+export const createNewVisit = ({ shortUrl, visit }) => ({ shortUrl, visit, type: CREATE_VISIT });
diff --git a/src/visits/services/provideServices.js b/src/visits/services/provideServices.js
index 9522f6e7..1f766785 100644
--- a/src/visits/services/provideServices.js
+++ b/src/visits/services/provideServices.js
@@ -1,9 +1,12 @@
import ShortUrlVisits from '../ShortUrlVisits';
-import { cancelGetShortUrlVisits, createNewVisit, getShortUrlVisits } from '../reducers/shortUrlVisits';
+import { cancelGetShortUrlVisits, getShortUrlVisits } from '../reducers/shortUrlVisits';
import { getShortUrlDetail } from '../reducers/shortUrlDetail';
import OpenMapModalBtn from '../helpers/OpenMapModalBtn';
import MapModal from '../helpers/MapModal';
import VisitsStats from '../VisitsStats';
+import { createNewVisit } from '../reducers/visitCreation';
+import { cancelGetTagVisits, getTagVisits } from '../reducers/tagVisits';
+import TagVisits from '../TagVisits';
import * as visitsParser from './VisitsParser';
const provideServices = (bottle, connect) => {
@@ -16,6 +19,11 @@ const provideServices = (bottle, connect) => {
[ 'shortUrlVisits', 'shortUrlDetail', 'mercureInfo', 'settings' ],
[ 'getShortUrlVisits', 'getShortUrlDetail', 'cancelGetShortUrlVisits', 'createNewVisit', 'loadMercureInfo' ]
));
+ bottle.serviceFactory('TagVisits', TagVisits, 'VisitsStats');
+ bottle.decorator('TagVisits', connect(
+ [ 'tagVisits', 'mercureInfo', 'settings' ],
+ [ 'getTagVisits', 'cancelGetTagVisits', 'createNewVisit', 'loadMercureInfo' ]
+ ));
// Services
bottle.serviceFactory('VisitsParser', () => visitsParser);
@@ -24,6 +32,10 @@ const provideServices = (bottle, connect) => {
bottle.serviceFactory('getShortUrlVisits', getShortUrlVisits, 'buildShlinkApiClient');
bottle.serviceFactory('getShortUrlDetail', getShortUrlDetail, 'buildShlinkApiClient');
bottle.serviceFactory('cancelGetShortUrlVisits', () => cancelGetShortUrlVisits);
+
+ bottle.serviceFactory('getTagVisits', getTagVisits, 'buildShlinkApiClient');
+ bottle.serviceFactory('cancelGetTagVisits', () => cancelGetTagVisits);
+
bottle.serviceFactory('createNewVisit', () => createNewVisit);
};
diff --git a/src/visits/types/index.js b/src/visits/types/index.js
index 941b4d61..b1464124 100644
--- a/src/visits/types/index.js
+++ b/src/visits/types/index.js
@@ -19,5 +19,6 @@ export const VisitType = PropTypes.shape({
export const VisitsInfoType = PropTypes.shape({
visits: PropTypes.arrayOf(VisitType),
loading: PropTypes.bool,
+ loadingLarge: PropTypes.bool,
error: PropTypes.bool,
});
diff --git a/test/short-urls/reducers/shortUrlsList.test.js b/test/short-urls/reducers/shortUrlsList.test.js
index c8e725c2..08bb0a82 100644
--- a/test/short-urls/reducers/shortUrlsList.test.js
+++ b/test/short-urls/reducers/shortUrlsList.test.js
@@ -7,7 +7,7 @@ import reducer, {
import { SHORT_URL_TAGS_EDITED } from '../../../src/short-urls/reducers/shortUrlTags';
import { SHORT_URL_DELETED } from '../../../src/short-urls/reducers/shortUrlDeletion';
import { SHORT_URL_META_EDITED } from '../../../src/short-urls/reducers/shortUrlMeta';
-import { CREATE_SHORT_URL_VISIT } from '../../../src/visits/reducers/shortUrlVisits';
+import { CREATE_VISIT } from '../../../src/visits/reducers/visitCreation';
describe('shortUrlsListReducer', () => {
describe('reducer', () => {
@@ -103,7 +103,7 @@ describe('shortUrlsListReducer', () => {
});
});
- it('updates visits count on CREATE_SHORT_URL_VISIT', () => {
+ it('updates visits count on CREATE_VISIT', () => {
const shortCode = 'abc123';
const shortUrl = {
shortCode,
@@ -119,7 +119,7 @@ describe('shortUrlsListReducer', () => {
},
};
- expect(reducer(state, { type: CREATE_SHORT_URL_VISIT, shortUrl })).toEqual({
+ expect(reducer(state, { type: CREATE_VISIT, shortUrl })).toEqual({
shortUrls: {
data: [
{ shortCode, domain: 'example.com', visitsCount: 5 },
diff --git a/test/utils/services/ShlinkApiClient.test.js b/test/utils/services/ShlinkApiClient.test.js
index 07f93a66..c1889f5d 100644
--- a/test/utils/services/ShlinkApiClient.test.js
+++ b/test/utils/services/ShlinkApiClient.test.js
@@ -71,6 +71,28 @@ describe('ShlinkApiClient', () => {
});
});
+ describe('getTagVisits', () => {
+ it('properly returns tag visits', async () => {
+ const expectedVisits = [ 'foo', 'bar' ];
+ const axiosSpy = jest.fn(createAxiosMock({
+ data: {
+ visits: {
+ data: expectedVisits,
+ },
+ },
+ }));
+ const { getTagVisits } = new ShlinkApiClient(axiosSpy);
+
+ const actualVisits = await getTagVisits('foo', {});
+
+ expect({ data: expectedVisits }).toEqual(actualVisits);
+ expect(axiosSpy).toHaveBeenCalledWith(expect.objectContaining({
+ url: '/tags/foo/visits',
+ method: 'GET',
+ }));
+ });
+ });
+
describe('getShortUrl', () => {
it.each(shortCodesWithDomainCombinations)('properly returns short URL', async (shortCode, domain) => {
const expectedShortUrl = { foo: 'bar' };
diff --git a/test/visits/reducers/shortUrlVisits.test.js b/test/visits/reducers/shortUrlVisits.test.js
index 5e2544f9..5bb27dfe 100644
--- a/test/visits/reducers/shortUrlVisits.test.js
+++ b/test/visits/reducers/shortUrlVisits.test.js
@@ -1,14 +1,13 @@
import reducer, {
getShortUrlVisits,
cancelGetShortUrlVisits,
- createNewVisit,
GET_SHORT_URL_VISITS_START,
GET_SHORT_URL_VISITS_ERROR,
GET_SHORT_URL_VISITS,
GET_SHORT_URL_VISITS_LARGE,
GET_SHORT_URL_VISITS_CANCEL,
- CREATE_SHORT_URL_VISIT,
} from '../../../src/visits/reducers/shortUrlVisits';
+import { CREATE_VISIT } from '../../../src/visits/reducers/visitCreation';
describe('shortUrlVisitsReducer', () => {
describe('reducer', () => {
@@ -54,7 +53,7 @@ describe('shortUrlVisitsReducer', () => {
it.each([
[{ shortCode: 'abc123' }, [{}, {}, {}]],
[{ shortCode: 'def456' }, [{}, {}]],
- ])('appends a new visit on CREATE_SHORT_URL_VISIT', (state, expectedVisits) => {
+ ])('appends a new visit on CREATE_VISIT', (state, expectedVisits) => {
const shortUrl = {
shortCode: 'abc123',
};
@@ -63,7 +62,7 @@ describe('shortUrlVisitsReducer', () => {
visits: [{}, {}],
};
- const { visits } = reducer(prevState, { type: CREATE_SHORT_URL_VISIT, shortUrl, visit: {} });
+ const { visits } = reducer(prevState, { type: CREATE_VISIT, shortUrl, visit: {} });
expect(visits).toEqual(expectedVisits);
});
@@ -138,11 +137,4 @@ describe('shortUrlVisitsReducer', () => {
it('just returns the action with proper type', () =>
expect(cancelGetShortUrlVisits()).toEqual({ type: GET_SHORT_URL_VISITS_CANCEL }));
});
-
- describe('createNewVisit', () => {
- it('just returns the action with proper type', () =>
- expect(createNewVisit({ shortUrl: {}, visit: {} })).toEqual(
- { type: CREATE_SHORT_URL_VISIT, shortUrl: {}, visit: {} }
- ));
- });
});
diff --git a/test/visits/reducers/visitCreation.test.js b/test/visits/reducers/visitCreation.test.js
new file mode 100644
index 00000000..e010255e
--- /dev/null
+++ b/test/visits/reducers/visitCreation.test.js
@@ -0,0 +1,10 @@
+import { CREATE_VISIT, createNewVisit } from '../../../src/visits/reducers/visitCreation';
+
+describe('visitCreationReducer', () => {
+ describe('createNewVisit', () => {
+ it('just returns the action with proper type', () =>
+ expect(createNewVisit({ shortUrl: {}, visit: {} })).toEqual(
+ { type: CREATE_VISIT, shortUrl: {}, visit: {} }
+ ));
+ });
+});