From bfbb21e1cc215ffe8adb23c64dd36570cfd58367 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 10 May 2020 19:02:58 +0200 Subject: [PATCH] Created page for tag visit stats --- src/common/MenuLayout.js | 13 ++- src/common/services/provideServices.js | 1 + src/reducers/index.js | 2 + src/short-urls/reducers/shortUrlsList.js | 4 +- src/utils/services/ShlinkApiClient.js | 4 + src/visits/TagVisits.js | 65 +++++++++++++++ src/visits/VisitsStats.js | 2 +- src/visits/reducers/common.js | 56 +++++++++++++ src/visits/reducers/shortUrlVisits.js | 72 +++-------------- src/visits/reducers/tagVisits.js | 81 +++++++++++++++++++ src/visits/reducers/visitCreation.js | 3 + src/visits/services/provideServices.js | 14 +++- src/visits/types/index.js | 1 + .../short-urls/reducers/shortUrlsList.test.js | 6 +- test/utils/services/ShlinkApiClient.test.js | 22 +++++ test/visits/reducers/shortUrlVisits.test.js | 14 +--- test/visits/reducers/visitCreation.test.js | 10 +++ 17 files changed, 291 insertions(+), 79 deletions(-) create mode 100644 src/visits/TagVisits.js create mode 100644 src/visits/reducers/common.js create mode 100644 src/visits/reducers/tagVisits.js create mode 100644 src/visits/reducers/visitCreation.js create mode 100644 test/visits/reducers/visitCreation.test.js 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: {} } + )); + }); +});