Merge pull request #188 from acelaya-forks/feature/visits-amount

Feature/visits amount
This commit is contained in:
Alejandro Celaya 2020-01-15 18:42:09 +01:00 committed by GitHub
commit 90751a09f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 39 additions and 33 deletions

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ExternalLink from '../../utils/ExternalLink'; import { ExternalLink } from 'react-external-link';
import { shortUrlTagsType } from '../reducers/shortUrlTags'; import { shortUrlTagsType } from '../reducers/shortUrlTags';
import { shortUrlType } from '../reducers/shortUrlsList'; import { shortUrlType } from '../reducers/shortUrlsList';

View file

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import { Modal, ModalBody, ModalHeader } from 'reactstrap'; import { Modal, ModalBody, ModalHeader } from 'reactstrap';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ExternalLink } from 'react-external-link';
import './PreviewModal.scss'; import './PreviewModal.scss';
import ExternalLink from '../../utils/ExternalLink';
const propTypes = { const propTypes = {
url: PropTypes.string, url: PropTypes.string,

View file

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import { Modal, ModalBody, ModalHeader } from 'reactstrap'; import { Modal, ModalBody, ModalHeader } from 'reactstrap';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ExternalLink } from 'react-external-link';
import './QrCodeModal.scss'; import './QrCodeModal.scss';
import ExternalLink from '../../utils/ExternalLink';
const propTypes = { const propTypes = {
url: PropTypes.string, url: PropTypes.string,

View file

@ -1,16 +1,17 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons'; import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
import { UncontrolledTooltip } from 'reactstrap'; import { UncontrolledTooltip } from 'reactstrap';
import { shortUrlType } from '../reducers/shortUrlsList'; import { shortUrlMetaType } from '../reducers/shortUrlsList';
import './ShortUrlVisitsCount.scss'; import './ShortUrlVisitsCount.scss';
const propTypes = { const propTypes = {
shortUrl: shortUrlType, visitsCount: PropTypes.number.isRequired,
meta: shortUrlMetaType,
}; };
const ShortUrlVisitsCount = ({ shortUrl }) => { const ShortUrlVisitsCount = ({ visitsCount, meta }) => {
const { visitsCount, meta } = shortUrl;
const maxVisits = meta && meta.maxVisits; const maxVisits = meta && meta.maxVisits;
if (!maxVisits) { if (!maxVisits) {

View file

@ -2,9 +2,9 @@ import { isEmpty } from 'ramda';
import React from 'react'; import React from 'react';
import Moment from 'react-moment'; import Moment from 'react-moment';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ExternalLink } from 'react-external-link';
import { shortUrlsListParamsType } from '../reducers/shortUrlsListParams'; import { shortUrlsListParamsType } from '../reducers/shortUrlsListParams';
import { serverType } from '../../servers/prop-types'; import { serverType } from '../../servers/prop-types';
import ExternalLink from '../../utils/ExternalLink';
import { shortUrlType } from '../reducers/shortUrlsList'; import { shortUrlType } from '../reducers/shortUrlsList';
import Tag from '../../tags/helpers/Tag'; import Tag from '../../tags/helpers/Tag';
import ShortUrlVisitsCount from './ShortUrlVisitsCount'; import ShortUrlVisitsCount from './ShortUrlVisitsCount';
@ -58,7 +58,7 @@ const ShortUrlsRow = (
</td> </td>
<td className="short-urls-row__cell" data-th="Tags: ">{this.renderTags(shortUrl.tags)}</td> <td className="short-urls-row__cell" data-th="Tags: ">{this.renderTags(shortUrl.tags)}</td>
<td className="short-urls-row__cell text-md-right" data-th="Visits: "> <td className="short-urls-row__cell text-md-right" data-th="Visits: ">
<ShortUrlVisitsCount shortUrl={shortUrl} /> <ShortUrlVisitsCount visitsCount={shortUrl.visitsCount} meta={shortUrl.meta} />
</td> </td>
<td className="short-urls-row__cell short-urls-row__cell--relative"> <td className="short-urls-row__cell short-urls-row__cell--relative">
<small <small

View file

@ -10,16 +10,18 @@ export const LIST_SHORT_URLS_ERROR = 'shlink/shortUrlsList/LIST_SHORT_URLS_ERROR
export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS'; export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS';
/* eslint-enable padding-line-between-statements */ /* eslint-enable padding-line-between-statements */
export const shortUrlMetaType = PropTypes.shape({
validSince: PropTypes.string,
validUntil: PropTypes.string,
maxVisits: PropTypes.number,
});
export const shortUrlType = PropTypes.shape({ export const shortUrlType = PropTypes.shape({
shortCode: PropTypes.string, shortCode: PropTypes.string,
shortUrl: PropTypes.string, shortUrl: PropTypes.string,
longUrl: PropTypes.string, longUrl: PropTypes.string,
visitsCount: PropTypes.number, visitsCount: PropTypes.number,
meta: PropTypes.shape({ meta: shortUrlMetaType,
validSince: PropTypes.string,
validUntil: PropTypes.string,
maxVisits: PropTypes.number,
}),
tags: PropTypes.arrayOf(PropTypes.string), tags: PropTypes.arrayOf(PropTypes.string),
}); });

View file

@ -1,3 +0,0 @@
import { ExternalLink } from 'react-external-link';
export default ExternalLink;

View file

@ -132,7 +132,7 @@ const ShortUrlVisits = (
return ( return (
<div className="shlink-container"> <div className="shlink-container">
<VisitsHeader shortUrlDetail={shortUrlDetail} /> <VisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} />
<section className="mt-4"> <section className="mt-4">
<DateRangeRow <DateRangeRow

View file

@ -1,17 +1,20 @@
import { Card, UncontrolledTooltip } from 'reactstrap'; import { Card, UncontrolledTooltip } from 'reactstrap';
import Moment from 'react-moment'; import Moment from 'react-moment';
import React from 'react'; import React from 'react';
import ExternalLink from '../utils/ExternalLink'; import { ExternalLink } from 'react-external-link';
import ShortUrlVisitsCount from '../short-urls/helpers/ShortUrlVisitsCount'; import ShortUrlVisitsCount from '../short-urls/helpers/ShortUrlVisitsCount';
import { shortUrlDetailType } from './reducers/shortUrlDetail'; import { shortUrlDetailType } from './reducers/shortUrlDetail';
import { shortUrlVisitsType } from './reducers/shortUrlVisits';
import './VisitsHeader.scss'; import './VisitsHeader.scss';
const propTypes = { const propTypes = {
shortUrlDetail: shortUrlDetailType.isRequired, shortUrlDetail: shortUrlDetailType.isRequired,
shortUrlVisits: shortUrlVisitsType.isRequired,
}; };
export default function VisitsHeader({ shortUrlDetail }) { export default function VisitsHeader({ shortUrlDetail, shortUrlVisits }) {
const { shortUrl, loading } = shortUrlDetail; const { shortUrl, loading } = shortUrlDetail;
const { visits } = shortUrlVisits;
const shortLink = shortUrl && shortUrl.shortUrl ? shortUrl.shortUrl : ''; const shortLink = shortUrl && shortUrl.shortUrl ? shortUrl.shortUrl : '';
const longLink = shortUrl && shortUrl.longUrl ? shortUrl.longUrl : ''; const longLink = shortUrl && shortUrl.longUrl ? shortUrl.longUrl : '';
@ -30,7 +33,7 @@ export default function VisitsHeader({ shortUrlDetail }) {
<h2> <h2>
<span className="badge badge-main float-right"> <span className="badge badge-main float-right">
Visits:{' '} Visits:{' '}
<ShortUrlVisitsCount shortUrl={shortUrl} /> <ShortUrlVisitsCount visitsCount={visits.length} meta={shortUrl.meta} />
</span> </span>
Visit stats for <ExternalLink href={shortLink} /> Visit stats for <ExternalLink href={shortLink} />
</h2> </h2>

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { ExternalLink } from 'react-external-link';
import PreviewModal from '../../../src/short-urls/helpers/PreviewModal'; import PreviewModal from '../../../src/short-urls/helpers/PreviewModal';
import ExternalLink from '../../../src/utils/ExternalLink';
describe('<PreviewModal />', () => { describe('<PreviewModal />', () => {
let wrapper; let wrapper;

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { ExternalLink } from 'react-external-link';
import QrCodeModal from '../../../src/short-urls/helpers/QrCodeModal'; import QrCodeModal from '../../../src/short-urls/helpers/QrCodeModal';
import ExternalLink from '../../../src/utils/ExternalLink';
describe('<QrCodeModal />', () => { describe('<QrCodeModal />', () => {
let wrapper; let wrapper;

View file

@ -1,22 +1,23 @@
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { UncontrolledTooltip } from 'reactstrap'; import { UncontrolledTooltip } from 'reactstrap';
import each from 'jest-each';
import ShortUrlVisitsCount from '../../../src/short-urls/helpers/ShortUrlVisitsCount'; import ShortUrlVisitsCount from '../../../src/short-urls/helpers/ShortUrlVisitsCount';
describe('<ShortUrlVisitsCount />', () => { describe('<ShortUrlVisitsCount />', () => {
let wrapper; let wrapper;
const createWrapper = (shortUrl) => { const createWrapper = (visitsCount, meta) => {
wrapper = shallow(<ShortUrlVisitsCount shortUrl={shortUrl} />); wrapper = shallow(<ShortUrlVisitsCount visitsCount={visitsCount} meta={meta} />);
return wrapper; return wrapper;
}; };
afterEach(() => wrapper && wrapper.unmount()); afterEach(() => wrapper && wrapper.unmount());
it('just returns visits when no maxVisits is provided', () => { each([ undefined, {}]).it('just returns visits when no maxVisits is provided', (meta) => {
const visitsCount = 45; const visitsCount = 45;
const wrapper = createWrapper({ visitsCount }); const wrapper = createWrapper(visitsCount, meta);
const maxVisitsHelper = wrapper.find('.short-urls-visits-count__max-visits-control'); const maxVisitsHelper = wrapper.find('.short-urls-visits-count__max-visits-control');
const maxVisitsTooltip = wrapper.find(UncontrolledTooltip); const maxVisitsTooltip = wrapper.find(UncontrolledTooltip);
@ -29,7 +30,7 @@ describe('<ShortUrlVisitsCount />', () => {
const visitsCount = 45; const visitsCount = 45;
const maxVisits = 500; const maxVisits = 500;
const meta = { maxVisits }; const meta = { maxVisits };
const wrapper = createWrapper({ visitsCount, meta }); const wrapper = createWrapper(visitsCount, meta);
const maxVisitsHelper = wrapper.find('.short-urls-visits-count__max-visits-control'); const maxVisitsHelper = wrapper.find('.short-urls-visits-count__max-visits-control');
const maxVisitsTooltip = wrapper.find(UncontrolledTooltip); const maxVisitsTooltip = wrapper.find(UncontrolledTooltip);

View file

@ -3,8 +3,8 @@ import { shallow } from 'enzyme';
import moment from 'moment'; import moment from 'moment';
import Moment from 'react-moment'; import Moment from 'react-moment';
import { assoc, toString } from 'ramda'; import { assoc, toString } from 'ramda';
import { ExternalLink } from 'react-external-link';
import createShortUrlsRow from '../../../src/short-urls/helpers/ShortUrlsRow'; import createShortUrlsRow from '../../../src/short-urls/helpers/ShortUrlsRow';
import ExternalLink from '../../../src/utils/ExternalLink';
import Tag from '../../../src/tags/helpers/Tag'; import Tag from '../../../src/tags/helpers/Tag';
describe('<ShortUrlsRow />', () => { describe('<ShortUrlsRow />', () => {

View file

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import Moment from 'react-moment'; import Moment from 'react-moment';
import { ExternalLink } from 'react-external-link';
import VisitsHeader from '../../src/visits/VisitsHeader'; import VisitsHeader from '../../src/visits/VisitsHeader';
import ExternalLink from '../../src/utils/ExternalLink';
describe('<VisitsHeader />', () => { describe('<VisitsHeader />', () => {
let wrapper; let wrapper;
@ -11,20 +11,22 @@ describe('<VisitsHeader />', () => {
shortUrl: 'https://doma.in/abc123', shortUrl: 'https://doma.in/abc123',
longUrl: 'https://foo.bar/bar/foo', longUrl: 'https://foo.bar/bar/foo',
dateCreated: '2018-01-01T10:00:00+01:00', dateCreated: '2018-01-01T10:00:00+01:00',
visitsCount: 3,
}, },
loading: false, loading: false,
}; };
const shortUrlVisits = {
visits: [{}, {}, {}],
};
beforeEach(() => { beforeEach(() => {
wrapper = shallow(<VisitsHeader shortUrlDetail={shortUrlDetail} />); wrapper = shallow(<VisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} />);
}); });
afterEach(() => wrapper.unmount()); afterEach(() => wrapper.unmount());
it('shows the amount of visits', () => { it('shows the amount of visits', () => {
const visitsBadge = wrapper.find('.badge'); const visitsBadge = wrapper.find('.badge');
expect(visitsBadge.html()).toContain(`Visits: <span>${shortUrlDetail.shortUrl.visitsCount}</span>`); expect(visitsBadge.html()).toContain(`Visits: <span>${shortUrlVisits.visits.length}</span>`);
}); });
it('shows when the URL was created', () => { it('shows when the URL was created', () => {