diff --git a/src/tags/TagsList.js b/src/tags/TagsList.js
index 54b18c97..f0e70874 100644
--- a/src/tags/TagsList.js
+++ b/src/tags/TagsList.js
@@ -4,6 +4,9 @@ import PropTypes from 'prop-types';
import Message from '../utils/Message';
import SearchField from '../utils/SearchField';
import { serverType } from '../servers/prop-types';
+import { MercureInfoType } from '../mercure/reducers/mercureInfo';
+import { SettingsType } from '../settings/reducers/settings';
+import { bindToMercureTopic } from '../mercure/helpers';
import { TagsListType } from './reducers/tagsList';
const { ceil } = Math;
@@ -14,15 +17,26 @@ const propTypes = {
forceListTags: PropTypes.func,
tagsList: TagsListType,
selectedServer: serverType,
+ createNewVisit: PropTypes.func,
+ loadMercureInfo: PropTypes.func,
+ mercureInfo: MercureInfoType,
+ settings: SettingsType,
};
const TagsList = (TagCard) => {
- const TagListComp = ({ filterTags, forceListTags, tagsList, selectedServer }) => {
+ const TagListComp = (
+ { filterTags, forceListTags, tagsList, selectedServer, createNewVisit, loadMercureInfo, mercureInfo, settings }
+ ) => {
+ const { realTimeUpdates } = settings;
const [ displayedTag, setDisplayedTag ] = useState();
useEffect(() => {
forceListTags();
}, []);
+ useEffect(
+ bindToMercureTopic(mercureInfo, realTimeUpdates, 'https://shlink.io/new-visit', createNewVisit, loadMercureInfo),
+ [ mercureInfo ]
+ );
const renderContent = () => {
if (tagsList.loading) {
diff --git a/src/tags/reducers/tagsList.js b/src/tags/reducers/tagsList.js
index e810f2c7..643b3a94 100644
--- a/src/tags/reducers/tagsList.js
+++ b/src/tags/reducers/tagsList.js
@@ -1,6 +1,7 @@
import { handleActions } from 'redux-actions';
import { isEmpty, reject } from 'ramda';
import PropTypes from 'prop-types';
+import { CREATE_VISIT } from '../../visits/reducers/visitCreation';
import { TAG_DELETED } from './tagDelete';
import { TAG_EDITED } from './tagEdit';
@@ -11,10 +12,15 @@ export const LIST_TAGS = 'shlink/tagsList/LIST_TAGS';
export const FILTER_TAGS = 'shlink/tagsList/FILTER_TAGS';
/* eslint-enable padding-line-between-statements */
+const TagStatsType = PropTypes.shape({
+ shortUrlsCount: PropTypes.number,
+ visitsCount: PropTypes.number,
+});
+
export const TagsListType = PropTypes.shape({
tags: PropTypes.arrayOf(PropTypes.string),
filteredTags: PropTypes.arrayOf(PropTypes.string),
- stats: PropTypes.object, // Record
+ stats: PropTypes.objectOf(TagStatsType), // Record
loading: PropTypes.bool,
error: PropTypes.bool,
});
@@ -29,11 +35,23 @@ const initialState = {
const renameTag = (oldName, newName) => (tag) => tag === oldName ? newName : tag;
const rejectTag = (tags, tagToReject) => reject((tag) => tag === tagToReject, tags);
+const increaseVisitsForTags = (tags, stats) => tags.reduce((stats, tag) => {
+ if (!stats[tag]) {
+ return stats;
+ }
+
+ const tagStats = stats[tag];
+
+ tagStats.visitsCount = tagStats.visitsCount + 1;
+ stats[tag] = tagStats;
+
+ return stats;
+}, { ...stats });
export default handleActions({
- [LIST_TAGS_START]: (state) => ({ ...state, loading: true, error: false }),
- [LIST_TAGS_ERROR]: (state) => ({ ...state, loading: false, error: true }),
- [LIST_TAGS]: (state, { tags, stats }) => ({ stats, tags, filteredTags: tags, loading: false, error: false }),
+ [LIST_TAGS_START]: () => ({ ...initialState, loading: true }),
+ [LIST_TAGS_ERROR]: () => ({ ...initialState, error: true }),
+ [LIST_TAGS]: (state, { tags, stats }) => ({ ...initialState, stats, tags, filteredTags: tags }),
[TAG_DELETED]: (state, { tag }) => ({
...state,
tags: rejectTag(state.tags, tag),
@@ -48,6 +66,10 @@ export default handleActions({
...state,
filteredTags: state.tags.filter((tag) => tag.toLowerCase().match(searchTerm)),
}),
+ [CREATE_VISIT]: (state, { shortUrl }) => ({
+ ...state,
+ stats: increaseVisitsForTags(shortUrl.tags, state.stats),
+ }),
}, initialState);
export const listTags = (buildShlinkApiClient, force = true) => () => async (dispatch, getState) => {
@@ -74,7 +96,4 @@ export const listTags = (buildShlinkApiClient, force = true) => () => async (dis
}
};
-export const filterTags = (searchTerm) => ({
- type: FILTER_TAGS,
- searchTerm,
-});
+export const filterTags = (searchTerm) => ({ type: FILTER_TAGS, searchTerm });
diff --git a/src/tags/services/provideServices.js b/src/tags/services/provideServices.js
index 566791ea..7917d068 100644
--- a/src/tags/services/provideServices.js
+++ b/src/tags/services/provideServices.js
@@ -28,7 +28,10 @@ const provideServices = (bottle, connect) => {
bottle.decorator('EditTagModal', connect([ 'tagEdit' ], [ 'editTag', 'tagEdited' ]));
bottle.serviceFactory('TagsList', TagsList, 'TagCard');
- bottle.decorator('TagsList', connect([ 'tagsList', 'selectedServer' ], [ 'forceListTags', 'filterTags' ]));
+ bottle.decorator('TagsList', connect(
+ [ 'tagsList', 'selectedServer', 'mercureInfo', 'settings' ],
+ [ 'forceListTags', 'filterTags', 'createNewVisit', 'loadMercureInfo' ]
+ ));
// Actions
const listTagsActionFactory = (force) => ({ buildShlinkApiClient }) => listTags(buildShlinkApiClient, force);
diff --git a/test/tags/TagsList.test.js b/test/tags/TagsList.test.js
index c3bd3393..b4ebcafa 100644
--- a/test/tags/TagsList.test.js
+++ b/test/tags/TagsList.test.js
@@ -15,7 +15,7 @@ describe('', () => {
const TagsList = createTagsList(TagCard);
wrapper = shallow(
-
+
);
return wrapper;
diff --git a/test/tags/reducers/tagsList.test.js b/test/tags/reducers/tagsList.test.js
index c312fea8..da86b564 100644
--- a/test/tags/reducers/tagsList.test.js
+++ b/test/tags/reducers/tagsList.test.js
@@ -11,17 +11,17 @@ import { TAG_EDITED } from '../../../src/tags/reducers/tagEdit';
describe('tagsListReducer', () => {
describe('reducer', () => {
it('returns loading on LIST_TAGS_START', () => {
- expect(reducer({}, { type: LIST_TAGS_START })).toEqual({
+ expect(reducer({}, { type: LIST_TAGS_START })).toEqual(expect.objectContaining({
loading: true,
error: false,
- });
+ }));
});
it('returns error on LIST_TAGS_ERROR', () => {
- expect(reducer({}, { type: LIST_TAGS_ERROR })).toEqual({
+ expect(reducer({}, { type: LIST_TAGS_ERROR })).toEqual(expect.objectContaining({
loading: false,
error: true,
- });
+ }));
});
it('returns provided tags as filtered and regular tags on LIST_TAGS', () => {