mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 17:40:23 +03:00
Ensured domain is passed when loading visits for a short URL on a specific domain
This commit is contained in:
parent
c682737505
commit
dc672bf0f0
14 changed files with 108 additions and 45 deletions
|
@ -3,25 +3,33 @@ 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 { shortUrlMetaType } from '../reducers/shortUrlMeta';
|
import { serverType } from '../../servers/prop-types';
|
||||||
|
import { shortUrlType } from '../reducers/shortUrlsList';
|
||||||
import './ShortUrlVisitsCount.scss';
|
import './ShortUrlVisitsCount.scss';
|
||||||
|
import VisitStatsLink from './VisitStatsLink';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
visitsCount: PropTypes.number.isRequired,
|
visitsCount: PropTypes.number.isRequired,
|
||||||
meta: shortUrlMetaType,
|
shortUrl: shortUrlType,
|
||||||
|
selectedServer: serverType,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ShortUrlVisitsCount = ({ visitsCount, meta }) => {
|
const ShortUrlVisitsCount = ({ visitsCount, shortUrl, selectedServer }) => {
|
||||||
const maxVisits = meta && meta.maxVisits;
|
const maxVisits = shortUrl && shortUrl.meta && shortUrl.meta.maxVisits;
|
||||||
|
const visitsLink = (
|
||||||
|
<VisitStatsLink selectedServer={selectedServer} shortUrl={shortUrl}>
|
||||||
|
<strong>{visitsCount}</strong>
|
||||||
|
</VisitStatsLink>
|
||||||
|
);
|
||||||
|
|
||||||
if (!maxVisits) {
|
if (!maxVisits) {
|
||||||
return <span>{visitsCount}</span>;
|
return visitsLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<span className="indivisible">
|
<span className="indivisible">
|
||||||
{visitsCount}
|
{visitsLink}
|
||||||
<small id="maxVisitsControl" className="short-urls-visits-count__max-visits-control">
|
<small id="maxVisitsControl" className="short-urls-visits-count__max-visits-control">
|
||||||
{' '}/ {maxVisits}{' '}
|
{' '}/ {maxVisits}{' '}
|
||||||
<sup>
|
<sup>
|
||||||
|
|
|
@ -58,7 +58,11 @@ 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 visitsCount={shortUrl.visitsCount} meta={shortUrl.meta} />
|
<ShortUrlVisitsCount
|
||||||
|
visitsCount={shortUrl.visitsCount}
|
||||||
|
shortUrl={shortUrl}
|
||||||
|
selectedServer={selectedServer}
|
||||||
|
/>
|
||||||
</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
|
||||||
|
|
|
@ -10,13 +10,13 @@ import {
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { ButtonDropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
|
import { ButtonDropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { serverType } from '../../servers/prop-types';
|
import { serverType } from '../../servers/prop-types';
|
||||||
import { shortUrlType } from '../reducers/shortUrlsList';
|
import { shortUrlType } from '../reducers/shortUrlsList';
|
||||||
import PreviewModal from './PreviewModal';
|
import PreviewModal from './PreviewModal';
|
||||||
import QrCodeModal from './QrCodeModal';
|
import QrCodeModal from './QrCodeModal';
|
||||||
|
import VisitStatsLink from './VisitStatsLink';
|
||||||
import './ShortUrlsRowMenu.scss';
|
import './ShortUrlsRowMenu.scss';
|
||||||
|
|
||||||
const ShortUrlsRowMenu = (
|
const ShortUrlsRowMenu = (
|
||||||
|
@ -57,7 +57,7 @@ const ShortUrlsRowMenu = (
|
||||||
<FontAwesomeIcon icon={menuIcon} />
|
<FontAwesomeIcon icon={menuIcon} />
|
||||||
</DropdownToggle>
|
</DropdownToggle>
|
||||||
<DropdownMenu right>
|
<DropdownMenu right>
|
||||||
<DropdownItem tag={Link} to={`/server/${selectedServer ? selectedServer.id : ''}/short-code/${shortUrl.shortCode}/visits`}>
|
<DropdownItem tag={VisitStatsLink} selectedServer={selectedServer} shortUrl={shortUrl}>
|
||||||
<FontAwesomeIcon icon={pieChartIcon} fixedWidth /> Visit stats
|
<FontAwesomeIcon icon={pieChartIcon} fixedWidth /> Visit stats
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
|
|
||||||
|
|
29
src/short-urls/helpers/VisitStatsLink.js
Normal file
29
src/short-urls/helpers/VisitStatsLink.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { serverType } from '../../servers/prop-types';
|
||||||
|
import { shortUrlType } from '../reducers/shortUrlsList';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
shortUrl: shortUrlType,
|
||||||
|
selectedServer: serverType,
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildVisitsUrl = ({ id }, { shortCode, domain }) => {
|
||||||
|
const query = domain ? `?domain=${domain}` : '';
|
||||||
|
|
||||||
|
return `/server/${id}/short-code/${shortCode}/visits${query}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const VisitStatsLink = ({ selectedServer, shortUrl, children, ...rest }) => {
|
||||||
|
if (!selectedServer || !shortUrl) {
|
||||||
|
return <span {...rest}>{children}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Link to={buildVisitsUrl(selectedServer, shortUrl)} {...rest}>{children}</Link>;
|
||||||
|
};
|
||||||
|
|
||||||
|
VisitStatsLink.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default VisitStatsLink;
|
|
@ -18,6 +18,7 @@ export const shortUrlType = PropTypes.shape({
|
||||||
visitsCount: PropTypes.number,
|
visitsCount: PropTypes.number,
|
||||||
meta: shortUrlMetaType,
|
meta: shortUrlMetaType,
|
||||||
tags: PropTypes.arrayOf(PropTypes.string),
|
tags: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
domain: PropTypes.string,
|
||||||
});
|
});
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
|
|
|
@ -12,6 +12,7 @@ export const apiErrorType = PropTypes.shape({
|
||||||
});
|
});
|
||||||
|
|
||||||
const buildShlinkBaseUrl = (url, apiVersion) => url ? `${url}/rest/v${apiVersion}` : '';
|
const buildShlinkBaseUrl = (url, apiVersion) => url ? `${url}/rest/v${apiVersion}` : '';
|
||||||
|
const rejectNilProps = (options = {}) => reject(isNil, options);
|
||||||
|
|
||||||
export default class ShlinkApiClient {
|
export default class ShlinkApiClient {
|
||||||
constructor(axios, baseUrl, apiKey) {
|
constructor(axios, baseUrl, apiKey) {
|
||||||
|
@ -22,7 +23,7 @@ export default class ShlinkApiClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
listShortUrls = pipe(
|
listShortUrls = pipe(
|
||||||
(options = {}) => reject(isNil, options),
|
rejectNilProps,
|
||||||
(options) => this._performRequest('/short-urls', 'GET', options).then((resp) => resp.data.shortUrls)
|
(options) => this._performRequest('/short-urls', 'GET', options).then((resp) => resp.data.shortUrls)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -37,20 +38,20 @@ export default class ShlinkApiClient {
|
||||||
this._performRequest(`/short-urls/${shortCode}/visits`, 'GET', query)
|
this._performRequest(`/short-urls/${shortCode}/visits`, 'GET', query)
|
||||||
.then((resp) => resp.data.visits);
|
.then((resp) => resp.data.visits);
|
||||||
|
|
||||||
getShortUrl = (shortCode) =>
|
getShortUrl = (shortCode, domain) =>
|
||||||
this._performRequest(`/short-urls/${shortCode}`, 'GET')
|
this._performRequest(`/short-urls/${shortCode}`, 'GET', { domain })
|
||||||
.then((resp) => resp.data);
|
.then((resp) => resp.data);
|
||||||
|
|
||||||
deleteShortUrl = (shortCode) =>
|
deleteShortUrl = (shortCode, domain) =>
|
||||||
this._performRequest(`/short-urls/${shortCode}`, 'DELETE')
|
this._performRequest(`/short-urls/${shortCode}`, 'DELETE', { domain })
|
||||||
.then(() => ({}));
|
.then(() => ({}));
|
||||||
|
|
||||||
updateShortUrlTags = (shortCode, tags) =>
|
updateShortUrlTags = (shortCode, domain, tags) =>
|
||||||
this._performRequest(`/short-urls/${shortCode}/tags`, 'PUT', {}, { tags })
|
this._performRequest(`/short-urls/${shortCode}/tags`, 'PUT', { domain }, { tags })
|
||||||
.then((resp) => resp.data.tags);
|
.then((resp) => resp.data.tags);
|
||||||
|
|
||||||
updateShortUrlMeta = (shortCode, meta) =>
|
updateShortUrlMeta = (shortCode, domain, meta) =>
|
||||||
this._performRequest(`/short-urls/${shortCode}`, 'PATCH', {}, meta)
|
this._performRequest(`/short-urls/${shortCode}`, 'PATCH', { domain }, meta)
|
||||||
.then(() => meta);
|
.then(() => meta);
|
||||||
|
|
||||||
listTags = () =>
|
listTags = () =>
|
||||||
|
@ -73,7 +74,7 @@ export default class ShlinkApiClient {
|
||||||
method,
|
method,
|
||||||
url: `${buildShlinkBaseUrl(this._baseUrl, this._apiVersion)}${url}`,
|
url: `${buildShlinkBaseUrl(this._baseUrl, this._apiVersion)}${url}`,
|
||||||
headers: { 'X-Api-Key': this._apiKey },
|
headers: { 'X-Api-Key': this._apiKey },
|
||||||
params: query,
|
params: rejectNilProps(query),
|
||||||
data: body,
|
data: body,
|
||||||
paramsSerializer: (params) => qs.stringify(params, { arrayFormat: 'brackets' }),
|
paramsSerializer: (params) => qs.stringify(params, { arrayFormat: 'brackets' }),
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { isEmpty, mapObjIndexed, values } from 'ramda';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Card } from 'reactstrap';
|
import { Card } from 'reactstrap';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import qs from 'qs';
|
||||||
import DateRangeRow from '../utils/DateRangeRow';
|
import DateRangeRow from '../utils/DateRangeRow';
|
||||||
import MutedMessage from '../utils/MuttedMessage';
|
import MutedMessage from '../utils/MuttedMessage';
|
||||||
import { formatDate } from '../utils/utils';
|
import { formatDate } from '../utils/utils';
|
||||||
|
@ -21,6 +22,9 @@ const ShortUrlVisits = (
|
||||||
match: PropTypes.shape({
|
match: PropTypes.shape({
|
||||||
params: PropTypes.object,
|
params: PropTypes.object,
|
||||||
}),
|
}),
|
||||||
|
location: PropTypes.shape({
|
||||||
|
search: PropTypes.string,
|
||||||
|
}),
|
||||||
getShortUrlVisits: PropTypes.func,
|
getShortUrlVisits: PropTypes.func,
|
||||||
shortUrlVisits: shortUrlVisitsType,
|
shortUrlVisits: shortUrlVisitsType,
|
||||||
getShortUrlDetail: PropTypes.func,
|
getShortUrlDetail: PropTypes.func,
|
||||||
|
@ -30,14 +34,16 @@ const ShortUrlVisits = (
|
||||||
|
|
||||||
state = { startDate: undefined, endDate: undefined };
|
state = { startDate: undefined, endDate: undefined };
|
||||||
loadVisits = () => {
|
loadVisits = () => {
|
||||||
const { match: { params }, getShortUrlVisits } = this.props;
|
const { match: { params }, location: { search }, getShortUrlVisits } = this.props;
|
||||||
const { shortCode } = params;
|
const { shortCode } = params;
|
||||||
const dates = mapObjIndexed(formatDate(), this.state);
|
const dates = mapObjIndexed(formatDate(), this.state);
|
||||||
const { startDate, endDate } = dates;
|
const { startDate, endDate } = dates;
|
||||||
|
const queryParams = qs.parse(search, { ignoreQueryPrefix: true });
|
||||||
|
const { domain } = queryParams;
|
||||||
|
|
||||||
// While the "page" is loaded, use the timestamp + filtering dates as memoization IDs for stats calcs
|
// While the "page" is loaded, use the timestamp + filtering dates as memoization IDs for stats calculations
|
||||||
this.memoizationId = `${this.timeWhenMounted}_${shortCode}_${startDate}_${endDate}`;
|
this.memoizationId = `${this.timeWhenMounted}_${shortCode}_${startDate}_${endDate}`;
|
||||||
getShortUrlVisits(shortCode, dates);
|
getShortUrlVisits(shortCode, { startDate, endDate, domain });
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
|
|
@ -33,7 +33,7 @@ export default function VisitsHeader({ shortUrlDetail, shortUrlVisits }) {
|
||||||
<h2>
|
<h2>
|
||||||
<span className="badge badge-main float-right">
|
<span className="badge badge-main float-right">
|
||||||
Visits:{' '}
|
Visits:{' '}
|
||||||
<ShortUrlVisitsCount visitsCount={visits.length} meta={shortUrl.meta} />
|
<ShortUrlVisitsCount visitsCount={visits.length} shortUrl={shortUrl} />
|
||||||
</span>
|
</span>
|
||||||
Visit stats for <ExternalLink href={shortLink} />
|
Visit stats for <ExternalLink href={shortLink} />
|
||||||
</h2>
|
</h2>
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default handleActions({
|
||||||
[GET_SHORT_URL_VISITS_CANCEL]: (state) => ({ ...state, cancelLoad: true }),
|
[GET_SHORT_URL_VISITS_CANCEL]: (state) => ({ ...state, cancelLoad: true }),
|
||||||
}, initialState);
|
}, initialState);
|
||||||
|
|
||||||
export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, dates) => async (dispatch, getState) => {
|
export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, query) => async (dispatch, getState) => {
|
||||||
dispatch({ type: GET_SHORT_URL_VISITS_START });
|
dispatch({ type: GET_SHORT_URL_VISITS_START });
|
||||||
|
|
||||||
const { getShortUrlVisits } = await buildShlinkApiClient(getState);
|
const { getShortUrlVisits } = await buildShlinkApiClient(getState);
|
||||||
|
@ -57,7 +57,7 @@ export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, dates) =>
|
||||||
const isLastPage = ({ currentPage, pagesCount }) => currentPage >= pagesCount;
|
const isLastPage = ({ currentPage, pagesCount }) => currentPage >= pagesCount;
|
||||||
|
|
||||||
const loadVisits = async (page = 1) => {
|
const loadVisits = async (page = 1) => {
|
||||||
const { pagination, data } = await getShortUrlVisits(shortCode, { ...dates, page, itemsPerPage });
|
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 was not returned, then this is an older shlink version. Just return data
|
||||||
if (!pagination || isLastPage(pagination)) {
|
if (!pagination || isLastPage(pagination)) {
|
||||||
|
@ -96,7 +96,7 @@ export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, dates) =>
|
||||||
const loadVisitsInParallel = (pages) =>
|
const loadVisitsInParallel = (pages) =>
|
||||||
Promise.all(pages.map(
|
Promise.all(pages.map(
|
||||||
(page) =>
|
(page) =>
|
||||||
getShortUrlVisits(shortCode, { ...dates, page, itemsPerPage })
|
getShortUrlVisits(shortCode, { ...query, page, itemsPerPage })
|
||||||
.then(prop('data'))
|
.then(prop('data'))
|
||||||
)).then(flatten);
|
)).then(flatten);
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@ import ShortUrlVisitsCount from '../../../src/short-urls/helpers/ShortUrlVisitsC
|
||||||
describe('<ShortUrlVisitsCount />', () => {
|
describe('<ShortUrlVisitsCount />', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
const createWrapper = (visitsCount, meta) => {
|
const createWrapper = (visitsCount, shortUrl) => {
|
||||||
wrapper = shallow(<ShortUrlVisitsCount visitsCount={visitsCount} meta={meta} />);
|
wrapper = shallow(<ShortUrlVisitsCount visitsCount={visitsCount} shortUrl={shortUrl} />);
|
||||||
|
|
||||||
return wrapper;
|
return wrapper;
|
||||||
};
|
};
|
||||||
|
@ -17,11 +17,11 @@ describe('<ShortUrlVisitsCount />', () => {
|
||||||
|
|
||||||
each([ undefined, {}]).it('just returns visits when no maxVisits is provided', (meta) => {
|
each([ undefined, {}]).it('just returns visits when no maxVisits is provided', (meta) => {
|
||||||
const visitsCount = 45;
|
const visitsCount = 45;
|
||||||
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);
|
||||||
|
|
||||||
expect(wrapper.html()).toEqual(`<span>${visitsCount}</span>`);
|
expect(wrapper.html()).toEqual(`<span><strong>${visitsCount}</strong></span>`);
|
||||||
expect(maxVisitsHelper).toHaveLength(0);
|
expect(maxVisitsHelper).toHaveLength(0);
|
||||||
expect(maxVisitsTooltip).toHaveLength(0);
|
expect(maxVisitsTooltip).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
@ -30,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);
|
||||||
|
|
||||||
|
|
|
@ -83,4 +83,6 @@ describe('<ShortUrlsRowMenu />', () => {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('generates expected visits page link', () => {})
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
|
import each from 'jest-each';
|
||||||
import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient';
|
import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient';
|
||||||
|
|
||||||
describe('ShlinkApiClient', () => {
|
describe('ShlinkApiClient', () => {
|
||||||
const createAxiosMock = (data) => () => Promise.resolve(data);
|
const createAxiosMock = (data) => () => Promise.resolve(data);
|
||||||
const createApiClient = (data) => new ShlinkApiClient(createAxiosMock(data));
|
const createApiClient = (data) => new ShlinkApiClient(createAxiosMock(data));
|
||||||
|
const shortCodesWithDomainCombinations = [
|
||||||
|
[ 'abc123', null ],
|
||||||
|
[ 'abc123', undefined ],
|
||||||
|
[ 'abc123', 'example.com' ],
|
||||||
|
];
|
||||||
|
|
||||||
describe('listShortUrls', () => {
|
describe('listShortUrls', () => {
|
||||||
it('properly returns short URLs list', async () => {
|
it('properly returns short URLs list', async () => {
|
||||||
|
@ -67,43 +73,45 @@ describe('ShlinkApiClient', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getShortUrl', () => {
|
describe('getShortUrl', () => {
|
||||||
it('properly returns short URL', async () => {
|
each(shortCodesWithDomainCombinations).it('properly returns short URL', async (shortCode, domain) => {
|
||||||
const expectedShortUrl = { foo: 'bar' };
|
const expectedShortUrl = { foo: 'bar' };
|
||||||
const axiosSpy = jest.fn(createAxiosMock({
|
const axiosSpy = jest.fn(createAxiosMock({
|
||||||
data: expectedShortUrl,
|
data: expectedShortUrl,
|
||||||
}));
|
}));
|
||||||
const { getShortUrl } = new ShlinkApiClient(axiosSpy);
|
const { getShortUrl } = new ShlinkApiClient(axiosSpy);
|
||||||
|
|
||||||
const result = await getShortUrl('abc123');
|
const result = await getShortUrl(shortCode, domain);
|
||||||
|
|
||||||
expect(expectedShortUrl).toEqual(result);
|
expect(expectedShortUrl).toEqual(result);
|
||||||
expect(axiosSpy).toHaveBeenCalledWith(expect.objectContaining({
|
expect(axiosSpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
url: '/short-urls/abc123',
|
url: `/short-urls/${shortCode}`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
params: domain ? { domain } : {},
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('updateShortUrlTags', () => {
|
describe('updateShortUrlTags', () => {
|
||||||
it('properly updates short URL tags', async () => {
|
each(shortCodesWithDomainCombinations).it('properly updates short URL tags', async (shortCode, domain) => {
|
||||||
const expectedTags = [ 'foo', 'bar' ];
|
const expectedTags = [ 'foo', 'bar' ];
|
||||||
const axiosSpy = jest.fn(createAxiosMock({
|
const axiosSpy = jest.fn(createAxiosMock({
|
||||||
data: { tags: expectedTags },
|
data: { tags: expectedTags },
|
||||||
}));
|
}));
|
||||||
const { updateShortUrlTags } = new ShlinkApiClient(axiosSpy);
|
const { updateShortUrlTags } = new ShlinkApiClient(axiosSpy);
|
||||||
|
|
||||||
const result = await updateShortUrlTags('abc123', expectedTags);
|
const result = await updateShortUrlTags(shortCode, domain, expectedTags);
|
||||||
|
|
||||||
expect(expectedTags).toEqual(result);
|
expect(expectedTags).toEqual(result);
|
||||||
expect(axiosSpy).toHaveBeenCalledWith(expect.objectContaining({
|
expect(axiosSpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
url: '/short-urls/abc123/tags',
|
url: `/short-urls/${shortCode}/tags`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
|
params: domain ? { domain } : {},
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('updateShortUrlMeta', () => {
|
describe('updateShortUrlMeta', () => {
|
||||||
it('properly updates short URL meta', async () => {
|
each(shortCodesWithDomainCombinations).it('properly updates short URL meta', async (shortCode, domain) => {
|
||||||
const expectedMeta = {
|
const expectedMeta = {
|
||||||
maxVisits: 50,
|
maxVisits: 50,
|
||||||
validSince: '2025-01-01T10:00:00+01:00',
|
validSince: '2025-01-01T10:00:00+01:00',
|
||||||
|
@ -111,12 +119,13 @@ describe('ShlinkApiClient', () => {
|
||||||
const axiosSpy = jest.fn(createAxiosMock());
|
const axiosSpy = jest.fn(createAxiosMock());
|
||||||
const { updateShortUrlMeta } = new ShlinkApiClient(axiosSpy);
|
const { updateShortUrlMeta } = new ShlinkApiClient(axiosSpy);
|
||||||
|
|
||||||
const result = await updateShortUrlMeta('abc123', expectedMeta);
|
const result = await updateShortUrlMeta(shortCode, domain, expectedMeta);
|
||||||
|
|
||||||
expect(expectedMeta).toEqual(result);
|
expect(expectedMeta).toEqual(result);
|
||||||
expect(axiosSpy).toHaveBeenCalledWith(expect.objectContaining({
|
expect(axiosSpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
url: '/short-urls/abc123',
|
url: `/short-urls/${shortCode}`,
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
|
params: domain ? { domain } : {},
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -172,15 +181,16 @@ describe('ShlinkApiClient', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deleteShortUrl', () => {
|
describe('deleteShortUrl', () => {
|
||||||
it('properly deletes provided short URL', async () => {
|
each(shortCodesWithDomainCombinations).it('properly deletes provided short URL', async (shortCode, domain) => {
|
||||||
const axiosSpy = jest.fn(createAxiosMock({}));
|
const axiosSpy = jest.fn(createAxiosMock({}));
|
||||||
const { deleteShortUrl } = new ShlinkApiClient(axiosSpy);
|
const { deleteShortUrl } = new ShlinkApiClient(axiosSpy);
|
||||||
|
|
||||||
await deleteShortUrl('abc123');
|
await deleteShortUrl(shortCode, domain);
|
||||||
|
|
||||||
expect(axiosSpy).toHaveBeenCalledWith(expect.objectContaining({
|
expect(axiosSpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
url: '/short-urls/abc123',
|
url: `/short-urls/${shortCode}`,
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
|
params: domain ? { domain } : {},
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,15 +17,17 @@ describe('<ShortUrlVisits />', () => {
|
||||||
const match = {
|
const match = {
|
||||||
params: { shortCode: 'abc123' },
|
params: { shortCode: 'abc123' },
|
||||||
};
|
};
|
||||||
|
const location = { search: '' };
|
||||||
|
|
||||||
const createComponent = (shortUrlVisits) => {
|
const createComponent = (shortUrlVisits) => {
|
||||||
const ShortUrlVisits = createShortUrlVisits({ processStatsFromVisits });
|
const ShortUrlVisits = createShortUrlVisits({ processStatsFromVisits }, () => '');
|
||||||
|
|
||||||
wrapper = shallow(
|
wrapper = shallow(
|
||||||
<ShortUrlVisits
|
<ShortUrlVisits
|
||||||
getShortUrlDetail={identity}
|
getShortUrlDetail={identity}
|
||||||
getShortUrlVisits={getShortUrlVisitsMock}
|
getShortUrlVisits={getShortUrlVisitsMock}
|
||||||
match={match}
|
match={match}
|
||||||
|
location={location}
|
||||||
shortUrlVisits={shortUrlVisits}
|
shortUrlVisits={shortUrlVisits}
|
||||||
shortUrlDetail={{}}
|
shortUrlDetail={{}}
|
||||||
cancelGetShortUrlVisits={identity}
|
cancelGetShortUrlVisits={identity}
|
||||||
|
|
|
@ -26,7 +26,7 @@ describe('<VisitsHeader />', () => {
|
||||||
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>${shortUrlVisits.visits.length}</span>`);
|
expect(visitsBadge.html()).toContain(`Visits: <span><strong>${shortUrlVisits.visits.length}</strong></span>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows when the URL was created', () => {
|
it('shows when the URL was created', () => {
|
||||||
|
|
Loading…
Reference in a new issue