Migrated to TS all visits components except the biggest two

This commit is contained in:
Alejandro Celaya 2020-09-04 19:33:16 +02:00
parent f2e7a2161d
commit 73b854037d
14 changed files with 85 additions and 173 deletions

View file

@ -1,4 +1,3 @@
import PropTypes from 'prop-types';
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { ShlinkMercureInfo } from '../../utils/services/types'; import { ShlinkMercureInfo } from '../../utils/services/types';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
@ -11,14 +10,6 @@ export const GET_MERCURE_INFO_ERROR = 'shlink/mercure/GET_MERCURE_INFO_ERROR';
export const GET_MERCURE_INFO = 'shlink/mercure/GET_MERCURE_INFO'; export const GET_MERCURE_INFO = 'shlink/mercure/GET_MERCURE_INFO';
/* eslint-enable padding-line-between-statements */ /* eslint-enable padding-line-between-statements */
/** @deprecated Use MercureInfo interface */
export const MercureInfoType = PropTypes.shape({
token: PropTypes.string,
mercureHubUrl: PropTypes.string,
loading: PropTypes.bool,
error: PropTypes.bool,
});
export interface MercureInfo { export interface MercureInfo {
token?: string; token?: string;
mercureHubUrl?: string; mercureHubUrl?: string;

View file

@ -1,4 +1,3 @@
import PropTypes from 'prop-types';
import { Dispatch, Action } from 'redux'; import { Dispatch, Action } from 'redux';
import { ShortUrlIdentifier, ShortUrlMeta } from '../data'; import { ShortUrlIdentifier, ShortUrlMeta } from '../data';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
@ -13,13 +12,6 @@ export const SHORT_URL_META_EDITED = 'shlink/shortUrlMeta/SHORT_URL_META_EDITED'
export const RESET_EDIT_SHORT_URL_META = 'shlink/shortUrlMeta/RESET_EDIT_SHORT_URL_META'; export const RESET_EDIT_SHORT_URL_META = 'shlink/shortUrlMeta/RESET_EDIT_SHORT_URL_META';
/* eslint-enable padding-line-between-statements */ /* eslint-enable padding-line-between-statements */
/** @deprecated Use ShortUrlMeta interface instead */
export const shortUrlMetaType = PropTypes.shape({
validSince: PropTypes.string,
validUntil: PropTypes.string,
maxVisits: PropTypes.number,
});
export interface ShortUrlMetaEdition { export interface ShortUrlMetaEdition {
shortCode: string | null; shortCode: string | null;
meta: ShortUrlMeta; meta: ShortUrlMeta;

View file

@ -1,5 +1,4 @@
import { assoc, assocPath, reject } from 'ramda'; import { assoc, assocPath, reject } from 'ramda';
import PropTypes from 'prop-types';
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { shortUrlMatches } from '../helpers'; import { shortUrlMatches } from '../helpers';
import { CREATE_VISIT, CreateVisitAction } from '../../visits/reducers/visitCreation'; import { CREATE_VISIT, CreateVisitAction } from '../../visits/reducers/visitCreation';
@ -10,7 +9,7 @@ import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuil
import { ShlinkShortUrlsResponse } from '../../utils/services/types'; import { ShlinkShortUrlsResponse } from '../../utils/services/types';
import { EditShortUrlTagsAction, SHORT_URL_TAGS_EDITED } from './shortUrlTags'; import { EditShortUrlTagsAction, SHORT_URL_TAGS_EDITED } from './shortUrlTags';
import { SHORT_URL_DELETED } from './shortUrlDeletion'; import { SHORT_URL_DELETED } from './shortUrlDeletion';
import { SHORT_URL_META_EDITED, ShortUrlMetaEditedAction, shortUrlMetaType } from './shortUrlMeta'; import { SHORT_URL_META_EDITED, ShortUrlMetaEditedAction } from './shortUrlMeta';
import { SHORT_URL_EDITED, ShortUrlEditedAction } from './shortUrlEdition'; import { SHORT_URL_EDITED, ShortUrlEditedAction } from './shortUrlEdition';
import { ShortUrlsListParams } from './shortUrlsListParams'; import { ShortUrlsListParams } from './shortUrlsListParams';
@ -20,17 +19,6 @@ 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 */
/** @deprecated Use ShortUrl interface instead */
export const shortUrlType = PropTypes.shape({
shortCode: PropTypes.string,
shortUrl: PropTypes.string,
longUrl: PropTypes.string,
visitsCount: PropTypes.number,
meta: shortUrlMetaType,
tags: PropTypes.arrayOf(PropTypes.string),
domain: PropTypes.string,
});
export interface ShortUrlsList { export interface ShortUrlsList {
shortUrls?: ShlinkShortUrlsResponse; shortUrls?: ShlinkShortUrlsResponse;
loading: boolean; loading: boolean;

View file

@ -1,4 +1,3 @@
import PropTypes from 'prop-types';
import { rangeOf } from '../utils'; import { rangeOf } from '../utils';
import LocalStorage from './LocalStorage'; import LocalStorage from './LocalStorage';
@ -36,9 +35,3 @@ export default class ColorGenerator {
return color; return color;
}; };
} }
/** @deprecated Use ColorGenerator class instead */
export const colorGeneratorType = PropTypes.shape({
getColorForKey: PropTypes.func,
setColorForKey: PropTypes.func,
});

View file

@ -7,7 +7,7 @@ import { ShortUrlVisits as ShortUrlVisitsState } from './reducers/shortUrlVisits
import ShortUrlVisitsHeader from './ShortUrlVisitsHeader'; import ShortUrlVisitsHeader from './ShortUrlVisitsHeader';
import { ShortUrlDetail } from './reducers/shortUrlDetail'; import { ShortUrlDetail } from './reducers/shortUrlDetail';
interface ShortUrlVisitsProps extends RouteComponentProps<{ shortCode: string }>, MercureBoundProps { export interface ShortUrlVisitsProps extends RouteComponentProps<{ shortCode: string }>, MercureBoundProps {
getShortUrlVisits: (shortCode: string, query?: ShlinkVisitsParams) => void; getShortUrlVisits: (shortCode: string, query?: ShlinkVisitsParams) => void;
shortUrlVisits: ShortUrlVisitsState; shortUrlVisits: ShortUrlVisitsState;
getShortUrlDetail: Function; getShortUrlDetail: Function;

View file

@ -1,24 +1,23 @@
import { UncontrolledTooltip } from 'reactstrap'; import { UncontrolledTooltip } from 'reactstrap';
import Moment from 'react-moment'; import Moment from 'react-moment';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { ExternalLink } from 'react-external-link'; import { ExternalLink } from 'react-external-link';
import { shortUrlDetailType } from './reducers/shortUrlDetail'; import { ShortUrlDetail } from './reducers/shortUrlDetail';
import { shortUrlVisitsType } from './reducers/shortUrlVisits'; import { ShortUrlVisits } from './reducers/shortUrlVisits';
import VisitsHeader from './VisitsHeader'; import VisitsHeader from './VisitsHeader';
import './ShortUrlVisitsHeader.scss'; import './ShortUrlVisitsHeader.scss';
const propTypes = { interface ShortUrlVisitsHeaderProps {
shortUrlDetail: shortUrlDetailType.isRequired, shortUrlDetail: ShortUrlDetail;
shortUrlVisits: shortUrlVisitsType.isRequired, shortUrlVisits: ShortUrlVisits;
goBack: PropTypes.func.isRequired, goBack: () => void;
}; }
const ShortUrlVisitsHeader = ({ shortUrlDetail, shortUrlVisits, goBack }) => { const ShortUrlVisitsHeader = ({ shortUrlDetail, shortUrlVisits, goBack }: ShortUrlVisitsHeaderProps) => {
const { shortUrl, loading } = shortUrlDetail; const { shortUrl, loading } = shortUrlDetail;
const { visits } = shortUrlVisits; const { visits } = shortUrlVisits;
const shortLink = shortUrl && shortUrl.shortUrl ? shortUrl.shortUrl : ''; const shortLink = shortUrl?.shortUrl ?? '';
const longLink = shortUrl && shortUrl.longUrl ? shortUrl.longUrl : ''; const longLink = shortUrl?.longUrl ?? '';
const renderDate = () => !shortUrl ? <small>Loading...</small> : ( const renderDate = () => !shortUrl ? <small>Loading...</small> : (
<span> <span>
@ -49,6 +48,4 @@ const ShortUrlVisitsHeader = ({ shortUrlDetail, shortUrlVisits, goBack }) => {
); );
}; };
ShortUrlVisitsHeader.propTypes = propTypes;
export default ShortUrlVisitsHeader; export default ShortUrlVisitsHeader;

View file

@ -1,52 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { MercureInfoType } from '../mercure/reducers/mercureInfo';
import { useMercureTopicBinding } from '../mercure/helpers';
import { TagVisitsType } from './reducers/tagVisits';
import TagVisitsHeader from './TagVisitsHeader';
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,
};
const TagVisits = (VisitsStats, colorGenerator) => {
const TagVisitsComp = ({
history,
match,
getTagVisits,
tagVisits,
cancelGetTagVisits,
createNewVisit,
loadMercureInfo,
mercureInfo,
}) => {
const { params } = match;
const { tag } = params;
const loadVisits = (dates) => getTagVisits(tag, dates);
useMercureTopicBinding(mercureInfo, 'https://shlink.io/new-visit', createNewVisit, loadMercureInfo);
return (
<VisitsStats getVisits={loadVisits} cancelGetVisits={cancelGetTagVisits} visitsInfo={tagVisits}>
<TagVisitsHeader tagVisits={tagVisits} goBack={history.goBack} colorGenerator={colorGenerator} />
</VisitsStats>
);
};
TagVisitsComp.propTypes = propTypes;
return TagVisitsComp;
};
export default TagVisits;

37
src/visits/TagVisits.tsx Normal file
View file

@ -0,0 +1,37 @@
import React, { FC } from 'react';
import { RouteComponentProps } from 'react-router';
import { MercureBoundProps, useMercureTopicBinding } from '../mercure/helpers';
import ColorGenerator from '../utils/services/ColorGenerator';
import { TagVisits as TagVisitsState } from './reducers/tagVisits';
import TagVisitsHeader from './TagVisitsHeader';
export interface TagVisitsProps extends RouteComponentProps<{ tag: string }>, MercureBoundProps {
getTagVisits: (tag: string, query: any) => void;
tagVisits: TagVisitsState;
cancelGetTagVisits: Function;
}
const TagVisits = (VisitsStats: FC<any>, colorGenerator: ColorGenerator) => ({ // TODO Use VisitsStatsProps once available
history: { goBack },
match,
getTagVisits,
tagVisits,
cancelGetTagVisits,
createNewVisit,
loadMercureInfo,
mercureInfo,
}: TagVisitsProps) => {
const { params } = match;
const { tag } = params;
const loadVisits = (dates: any) => getTagVisits(tag, dates);
useMercureTopicBinding(mercureInfo, 'https://shlink.io/new-visit', createNewVisit, loadMercureInfo);
return (
<VisitsStats getVisits={loadVisits} cancelGetVisits={cancelGetTagVisits} visitsInfo={tagVisits}>
<TagVisitsHeader tagVisits={tagVisits} goBack={goBack} colorGenerator={colorGenerator} />
</VisitsStats>
);
};
export default TagVisits;

View file

@ -1,18 +1,17 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import Tag from '../tags/helpers/Tag'; import Tag from '../tags/helpers/Tag';
import { colorGeneratorType } from '../utils/services/ColorGenerator'; import ColorGenerator from '../utils/services/ColorGenerator';
import VisitsHeader from './VisitsHeader'; import VisitsHeader from './VisitsHeader';
import { TagVisitsType } from './reducers/tagVisits'; import { TagVisits } from './reducers/tagVisits';
import './ShortUrlVisitsHeader.scss'; import './ShortUrlVisitsHeader.scss';
const propTypes = { interface TagVisitsHeader {
tagVisits: TagVisitsType.isRequired, tagVisits: TagVisits;
goBack: PropTypes.func.isRequired, goBack: () => void;
colorGenerator: colorGeneratorType, colorGenerator: ColorGenerator;
}; }
const TagVisitsHeader = ({ tagVisits, goBack, colorGenerator }) => { const TagVisitsHeader = ({ tagVisits, goBack, colorGenerator }: TagVisitsHeader) => {
const { visits, tag } = tagVisits; const { visits, tag } = tagVisits;
const visitsStatsTitle = ( const visitsStatsTitle = (
@ -25,6 +24,4 @@ const TagVisitsHeader = ({ tagVisits, goBack, colorGenerator }) => {
return <VisitsHeader title={visitsStatsTitle} goBack={goBack} visits={visits} />; return <VisitsHeader title={visitsStatsTitle} goBack={goBack} visits={visits} />;
}; };
TagVisitsHeader.propTypes = propTypes;
export default TagVisitsHeader; export default TagVisitsHeader;

View file

@ -1,21 +1,19 @@
import { Button, Card } from 'reactstrap'; import { Button, Card } from 'reactstrap';
import React from 'react'; import React, { FC, ReactNode } from 'react';
import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faArrowLeft } from '@fortawesome/free-solid-svg-icons'; import { faArrowLeft } from '@fortawesome/free-solid-svg-icons';
import ShortUrlVisitsCount from '../short-urls/helpers/ShortUrlVisitsCount'; import ShortUrlVisitsCount from '../short-urls/helpers/ShortUrlVisitsCount';
import { shortUrlType } from '../short-urls/reducers/shortUrlsList'; import { ShortUrl } from '../short-urls/data';
import { VisitType } from './types'; import { Visit } from './types';
const propTypes = { interface VisitsHeaderProps {
visits: PropTypes.arrayOf(VisitType).isRequired, visits: Visit[];
goBack: PropTypes.func.isRequired, goBack: () => void;
title: PropTypes.node.isRequired, title: ReactNode;
children: PropTypes.node, shortUrl?: ShortUrl;
shortUrl: shortUrlType, }
};
const VisitsHeader = ({ visits, goBack, shortUrl, children, title }) => ( const VisitsHeader: FC<VisitsHeaderProps> = ({ visits, goBack, shortUrl, children, title }) => (
<header> <header>
<Card className="bg-light" body> <Card className="bg-light" body>
<h2 className="d-flex justify-content-between align-items-center mb-0"> <h2 className="d-flex justify-content-between align-items-center mb-0">
@ -39,6 +37,4 @@ const VisitsHeader = ({ visits, goBack, shortUrl, children, title }) => (
</header> </header>
); );
VisitsHeader.propTypes = propTypes;
export default VisitsHeader; export default VisitsHeader;

View file

@ -1,6 +1,4 @@
import PropTypes from 'prop-types';
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { shortUrlType } from '../../short-urls/reducers/shortUrlsList';
import { ShortUrl } from '../../short-urls/data'; import { ShortUrl } from '../../short-urls/data';
import { buildReducer } from '../../utils/helpers/redux'; import { buildReducer } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
@ -13,13 +11,6 @@ export const GET_SHORT_URL_DETAIL_ERROR = 'shlink/shortUrlDetail/GET_SHORT_URL_D
export const GET_SHORT_URL_DETAIL = 'shlink/shortUrlDetail/GET_SHORT_URL_DETAIL'; export const GET_SHORT_URL_DETAIL = 'shlink/shortUrlDetail/GET_SHORT_URL_DETAIL';
/* eslint-enable padding-line-between-statements */ /* eslint-enable padding-line-between-statements */
/** @deprecated Use ShortUrlDetail interface instead */
export const shortUrlDetailType = PropTypes.shape({
shortUrl: shortUrlType,
loading: PropTypes.bool,
error: PropTypes.bool,
});
export interface ShortUrlDetail { export interface ShortUrlDetail {
shortUrl?: ShortUrl; shortUrl?: ShortUrl;
loading: boolean; loading: boolean;

View file

@ -1,7 +1,6 @@
import PropTypes from 'prop-types';
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { shortUrlMatches } from '../../short-urls/helpers'; import { shortUrlMatches } from '../../short-urls/helpers';
import { Visit, VisitsInfo, VisitsLoadProgressChangedAction, VisitType } from '../types'; import { Visit, VisitsInfo, VisitsLoadProgressChangedAction } from '../types';
import { ShortUrlIdentifier } from '../../short-urls/data'; import { ShortUrlIdentifier } from '../../short-urls/data';
import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
@ -19,17 +18,6 @@ export const GET_SHORT_URL_VISITS_CANCEL = 'shlink/shortUrlVisits/GET_SHORT_URL_
export const GET_SHORT_URL_VISITS_PROGRESS_CHANGED = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_PROGRESS_CHANGED'; export const GET_SHORT_URL_VISITS_PROGRESS_CHANGED = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_PROGRESS_CHANGED';
/* eslint-enable padding-line-between-statements */ /* eslint-enable padding-line-between-statements */
/** @deprecated Use ShortUrlVisits interface instead */
export const shortUrlVisitsType = PropTypes.shape({
visits: PropTypes.arrayOf(VisitType),
shortCode: PropTypes.string,
domain: PropTypes.string,
loading: PropTypes.bool,
loadingLarge: PropTypes.bool,
error: PropTypes.bool,
progress: PropTypes.number,
});
export interface ShortUrlVisits extends VisitsInfo, ShortUrlIdentifier {} export interface ShortUrlVisits extends VisitsInfo, ShortUrlIdentifier {}
interface ShortUrlVisitsAction extends Action<string>, ShortUrlIdentifier { interface ShortUrlVisitsAction extends Action<string>, ShortUrlIdentifier {

View file

@ -1,6 +1,5 @@
import PropTypes from 'prop-types';
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { Visit, VisitsInfo, VisitsLoadProgressChangedAction, VisitType } from '../types'; import { Visit, VisitsInfo, VisitsLoadProgressChangedAction } from '../types';
import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
@ -16,16 +15,6 @@ export const GET_TAG_VISITS_CANCEL = 'shlink/tagVisits/GET_TAG_VISITS_CANCEL';
export const GET_TAG_VISITS_PROGRESS_CHANGED = 'shlink/tagVisits/GET_TAG_VISITS_PROGRESS_CHANGED'; export const GET_TAG_VISITS_PROGRESS_CHANGED = 'shlink/tagVisits/GET_TAG_VISITS_PROGRESS_CHANGED';
/* eslint-enable padding-line-between-statements */ /* eslint-enable padding-line-between-statements */
/** @deprecated Use TagVisits interface instead */
export const TagVisitsType = PropTypes.shape({
visits: PropTypes.arrayOf(VisitType),
tag: PropTypes.string,
loading: PropTypes.bool,
loadingLarge: PropTypes.bool,
error: PropTypes.bool,
progress: PropTypes.number,
});
export interface TagVisits extends VisitsInfo { export interface TagVisits extends VisitsInfo {
tag: string; tag: string;
} }

View file

@ -1,19 +1,24 @@
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow, ShallowWrapper } from 'enzyme';
import { identity } from 'ramda'; import { identity } from 'ramda';
import createShortUrlVisits from '../../src/visits/ShortUrlVisits'; import { Mock } from 'ts-mockery';
import { History, Location } from 'history';
import { match } from 'react-router'; // eslint-disable-line @typescript-eslint/no-unused-vars
import createShortUrlVisits, { ShortUrlVisitsProps } from '../../src/visits/ShortUrlVisits';
import ShortUrlVisitsHeader from '../../src/visits/ShortUrlVisitsHeader'; import ShortUrlVisitsHeader from '../../src/visits/ShortUrlVisitsHeader';
import { ShortUrlVisits as ShortUrlVisitsState } from '../../src/visits/reducers/shortUrlVisits';
import { ShortUrlDetail } from '../../src/visits/reducers/shortUrlDetail';
describe('<ShortUrlVisits />', () => { describe('<ShortUrlVisits />', () => {
let wrapper; let wrapper: ShallowWrapper;
const getShortUrlVisitsMock = jest.fn(); const getShortUrlVisitsMock = jest.fn();
const match = { const match = Mock.of<match<{ shortCode: string }>>({
params: { shortCode: 'abc123' }, params: { shortCode: 'abc123' },
}; });
const location = { search: '' }; const location = Mock.of<Location>({ search: '' });
const history = { const history = Mock.of<History>({
goBack: jest.fn(), goBack: jest.fn(),
}; });
const VisitsStats = jest.fn(); const VisitsStats = jest.fn();
beforeEach(() => { beforeEach(() => {
@ -21,15 +26,15 @@ describe('<ShortUrlVisits />', () => {
wrapper = shallow( wrapper = shallow(
<ShortUrlVisits <ShortUrlVisits
{...Mock.all<ShortUrlVisitsProps>()}
getShortUrlDetail={identity} getShortUrlDetail={identity}
getShortUrlVisits={getShortUrlVisitsMock} getShortUrlVisits={getShortUrlVisitsMock}
match={match} match={match}
location={location} location={location}
history={history} history={history}
shortUrlVisits={{ loading: true, visits: [] }} shortUrlVisits={Mock.of<ShortUrlVisitsState>({ loading: true, visits: [] })}
shortUrlDetail={{}} shortUrlDetail={Mock.all<ShortUrlDetail>()}
cancelGetShortUrlVisits={identity} cancelGetShortUrlVisits={identity}
matchMedia={() => ({ matches: false })}
/>, />,
); );
}); });