mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-09 01:37:24 +03:00
Added real time updates to tags list page
This commit is contained in:
parent
e47dfaf36f
commit
1d26cd93fb
5 changed files with 51 additions and 15 deletions
|
@ -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) {
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -15,7 +15,7 @@ describe('<TagsList />', () => {
|
|||
const TagsList = createTagsList(TagCard);
|
||||
|
||||
wrapper = shallow(
|
||||
<TagsList forceListTags={identity} filterTags={filterTags} match={{ params }} tagsList={tagsList} />
|
||||
<TagsList forceListTags={identity} filterTags={filterTags} match={{ params }} tagsList={tagsList} settings={{}} />
|
||||
);
|
||||
|
||||
return wrapper;
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
Loading…
Reference in a new issue