mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-09 01:37:24 +03:00
Moved all visits-related services to its own service provide function inside visits
This commit is contained in:
parent
471322f4db
commit
fa3e1eba93
9 changed files with 151 additions and 147 deletions
|
@ -5,96 +5,96 @@ import burgerIcon from '@fortawesome/fontawesome-free-solid/faBars';
|
|||
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
||||
import classnames from 'classnames';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import ShortUrlsVisits from '../visits/ShortUrlVisits';
|
||||
import './MenuLayout.scss';
|
||||
import { serverType } from '../servers/prop-types';
|
||||
import './MenuLayout.scss';
|
||||
|
||||
const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl) => class MenuLayout extends React.Component {
|
||||
static propTypes = {
|
||||
match: PropTypes.object,
|
||||
selectServer: PropTypes.func,
|
||||
location: PropTypes.object,
|
||||
selectedServer: serverType,
|
||||
const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisits) =>
|
||||
class MenuLayout extends React.Component {
|
||||
static propTypes = {
|
||||
match: PropTypes.object,
|
||||
selectServer: PropTypes.func,
|
||||
location: PropTypes.object,
|
||||
selectedServer: serverType,
|
||||
};
|
||||
|
||||
state = { showSideBar: false };
|
||||
|
||||
// FIXME Shouldn't use componentWillMount, but this code has to be run before children components are rendered
|
||||
/* eslint react/no-deprecated: "off" */
|
||||
componentWillMount() {
|
||||
const { match, selectServer } = this.props;
|
||||
const { params: { serverId } } = match;
|
||||
|
||||
selectServer(serverId);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { location } = this.props;
|
||||
|
||||
// Hide sidebar when location changes
|
||||
if (location !== prevProps.location) {
|
||||
this.setState({ showSideBar: false });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { selectedServer } = this.props;
|
||||
const burgerClasses = classnames('menu-layout__burger-icon', {
|
||||
'menu-layout__burger-icon--active': this.state.showSideBar,
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<FontAwesomeIcon
|
||||
icon={burgerIcon}
|
||||
className={burgerClasses}
|
||||
onClick={() => this.setState(({ showSideBar }) => ({ showSideBar: !showSideBar }))}
|
||||
/>
|
||||
|
||||
<Swipeable
|
||||
delta={40}
|
||||
className="menu-layout__swipeable"
|
||||
onSwipedLeft={() => this.setState({ showSideBar: false })}
|
||||
onSwipedRight={() => this.setState({ showSideBar: true })}
|
||||
>
|
||||
<div className="row menu-layout__swipeable-inner">
|
||||
<AsideMenu
|
||||
className="col-lg-2 col-md-3"
|
||||
selectedServer={selectedServer}
|
||||
showOnMobile={this.state.showSideBar}
|
||||
/>
|
||||
<div
|
||||
className="col-lg-10 offset-lg-2 col-md-9 offset-md-3"
|
||||
onClick={() => this.setState({ showSideBar: false })}
|
||||
>
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path="/server/:serverId/list-short-urls/:page"
|
||||
component={ShortUrls}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/server/:serverId/create-short-url"
|
||||
component={CreateShortUrl}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/server/:serverId/short-code/:shortCode/visits"
|
||||
component={ShortUrlVisits}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/server/:serverId/manage-tags"
|
||||
component={TagsList}
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
</Swipeable>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
state = { showSideBar: false };
|
||||
|
||||
// FIXME Shouldn't use componentWillMount, but this code has to be run before children components are rendered
|
||||
/* eslint react/no-deprecated: "off" */
|
||||
componentWillMount() {
|
||||
const { match, selectServer } = this.props;
|
||||
const { params: { serverId } } = match;
|
||||
|
||||
selectServer(serverId);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { location } = this.props;
|
||||
|
||||
// Hide sidebar when location changes
|
||||
if (location !== prevProps.location) {
|
||||
this.setState({ showSideBar: false });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { selectedServer } = this.props;
|
||||
const burgerClasses = classnames('menu-layout__burger-icon', {
|
||||
'menu-layout__burger-icon--active': this.state.showSideBar,
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<FontAwesomeIcon
|
||||
icon={burgerIcon}
|
||||
className={burgerClasses}
|
||||
onClick={() => this.setState(({ showSideBar }) => ({ showSideBar: !showSideBar }))}
|
||||
/>
|
||||
|
||||
<Swipeable
|
||||
delta={40}
|
||||
className="menu-layout__swipeable"
|
||||
onSwipedLeft={() => this.setState({ showSideBar: false })}
|
||||
onSwipedRight={() => this.setState({ showSideBar: true })}
|
||||
>
|
||||
<div className="row menu-layout__swipeable-inner">
|
||||
<AsideMenu
|
||||
className="col-lg-2 col-md-3"
|
||||
selectedServer={selectedServer}
|
||||
showOnMobile={this.state.showSideBar}
|
||||
/>
|
||||
<div
|
||||
className="col-lg-10 offset-lg-2 col-md-9 offset-md-3"
|
||||
onClick={() => this.setState({ showSideBar: false })}
|
||||
>
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path="/server/:serverId/list-short-urls/:page"
|
||||
component={ShortUrls}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/server/:serverId/create-short-url"
|
||||
component={CreateShortUrl}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/server/:serverId/short-code/:shortCode/visits"
|
||||
component={ShortUrlsVisits}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/server/:serverId/manage-tags"
|
||||
component={TagsList}
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
</Swipeable>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default MenuLayout;
|
||||
|
|
|
@ -45,6 +45,7 @@ import DeleteTagConfirmModal from '../tags/helpers/DeleteTagConfirmModal';
|
|||
import { deleteTag, tagDeleted } from '../tags/reducers/tagDelete';
|
||||
import EditTagModal from '../tags/helpers/EditTagModal';
|
||||
import { editTag, tagEdited } from '../tags/reducers/tagEdit';
|
||||
import provideVisitsServices from '../visits/container/provideServices';
|
||||
|
||||
const bottle = new Bottle();
|
||||
const { container } = bottle;
|
||||
|
@ -70,7 +71,15 @@ bottle.decorator('MainHeader', withRouter);
|
|||
bottle.serviceFactory('Home', () => Home);
|
||||
bottle.decorator('Home', connectDecorator([ 'servers' ], { resetSelectedServer }));
|
||||
|
||||
bottle.serviceFactory('MenuLayout', MenuLayout, 'TagsList', 'ShortUrls', 'AsideMenu', 'CreateShortUrl');
|
||||
bottle.serviceFactory(
|
||||
'MenuLayout',
|
||||
MenuLayout,
|
||||
'TagsList',
|
||||
'ShortUrls',
|
||||
'AsideMenu',
|
||||
'CreateShortUrl',
|
||||
'ShortUrlVisits'
|
||||
);
|
||||
bottle.decorator('MenuLayout', connectDecorator([ 'selectedServer', 'shortUrlsListParams' ], { selectServer }));
|
||||
bottle.decorator('MenuLayout', withRouter);
|
||||
|
||||
|
@ -161,4 +170,6 @@ bottle.decorator('DeleteTagConfirmModal', connectDecorator([ 'tagDelete' ], { de
|
|||
bottle.serviceFactory('EditTagModal', EditTagModal, 'ColorGenerator');
|
||||
bottle.decorator('EditTagModal', connectDecorator([ 'tagEdit' ], { editTag, tagEdited }));
|
||||
|
||||
provideVisitsServices(bottle, connectDecorator);
|
||||
|
||||
export default container;
|
||||
|
|
|
@ -1,31 +1,25 @@
|
|||
import preloader from '@fortawesome/fontawesome-free-solid/faCircleNotch';
|
||||
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
||||
import { isEmpty, mapObjIndexed, pick } from 'ramda';
|
||||
import { isEmpty, mapObjIndexed } from 'ramda';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Card } from 'reactstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import DateInput from '../utils/DateInput';
|
||||
import MutedMessage from '../utils/MuttedMessage';
|
||||
import SortableBarGraph from './SortableBarGraph';
|
||||
import { getShortUrlVisits, shortUrlVisitsType } from './reducers/shortUrlVisits';
|
||||
import {
|
||||
processBrowserStats,
|
||||
processCountriesStats,
|
||||
processOsStats,
|
||||
processReferrersStats,
|
||||
} from './services/VisitsParser';
|
||||
import { shortUrlVisitsType } from './reducers/shortUrlVisits';
|
||||
import { VisitsHeader } from './VisitsHeader';
|
||||
import GraphCard from './GraphCard';
|
||||
import { getShortUrlDetail, shortUrlDetailType } from './reducers/shortUrlDetail';
|
||||
import { shortUrlDetailType } from './reducers/shortUrlDetail';
|
||||
import './ShortUrlVisits.scss';
|
||||
|
||||
export class ShortUrlsVisitsComponent extends React.Component {
|
||||
const ShortUrlVisits = ({
|
||||
processOsStats,
|
||||
processBrowserStats,
|
||||
processCountriesStats,
|
||||
processReferrersStats,
|
||||
}) => class ShortUrlVisits extends React.Component {
|
||||
static propTypes = {
|
||||
processOsStats: PropTypes.func,
|
||||
processBrowserStats: PropTypes.func,
|
||||
processCountriesStats: PropTypes.func,
|
||||
processReferrersStats: PropTypes.func,
|
||||
match: PropTypes.shape({
|
||||
params: PropTypes.object,
|
||||
}),
|
||||
|
@ -34,12 +28,6 @@ export class ShortUrlsVisitsComponent extends React.Component {
|
|||
getShortUrlDetail: PropTypes.func,
|
||||
shortUrlDetail: shortUrlDetailType,
|
||||
};
|
||||
static defaultProps = {
|
||||
processOsStats,
|
||||
processBrowserStats,
|
||||
processCountriesStats,
|
||||
processReferrersStats,
|
||||
};
|
||||
|
||||
state = { startDate: undefined, endDate: undefined };
|
||||
loadVisits = () => {
|
||||
|
@ -59,14 +47,7 @@ export class ShortUrlsVisitsComponent extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
processOsStats,
|
||||
processBrowserStats,
|
||||
processCountriesStats,
|
||||
processReferrersStats,
|
||||
shortUrlVisits,
|
||||
shortUrlDetail,
|
||||
} = this.props;
|
||||
const { shortUrlVisits, shortUrlDetail } = this.props;
|
||||
|
||||
const renderVisitsContent = () => {
|
||||
const { visits, loading, error } = shortUrlVisits;
|
||||
|
@ -153,11 +134,6 @@ export class ShortUrlsVisitsComponent extends React.Component {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const ShortUrlsVisits = connect(
|
||||
pick([ 'shortUrlVisits', 'shortUrlDetail' ]),
|
||||
{ getShortUrlVisits, getShortUrlDetail }
|
||||
)(ShortUrlsVisitsComponent);
|
||||
|
||||
export default ShortUrlsVisits;
|
||||
export default ShortUrlVisits;
|
||||
|
|
22
src/visits/container/provideServices.js
Normal file
22
src/visits/container/provideServices.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import ShortUrlVisits from '../ShortUrlVisits';
|
||||
import { getShortUrlVisits } from '../reducers/shortUrlVisits';
|
||||
import { getShortUrlDetail } from '../reducers/shortUrlDetail';
|
||||
import * as visitsParser from '../services/VisitsParser';
|
||||
|
||||
const provideServices = (bottle, connect) => {
|
||||
// Components
|
||||
bottle.serviceFactory('ShortUrlVisits', ShortUrlVisits, 'VisitsParser');
|
||||
bottle.decorator('ShortUrlVisits', connect(
|
||||
[ 'shortUrlVisits', 'shortUrlDetail' ],
|
||||
[ 'getShortUrlVisits', 'getShortUrlDetail' ]
|
||||
));
|
||||
|
||||
// Services
|
||||
bottle.serviceFactory('VisitsParser', () => visitsParser);
|
||||
|
||||
// Actions
|
||||
bottle.serviceFactory('getShortUrlVisits', getShortUrlVisits, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('getShortUrlDetail', getShortUrlDetail, 'buildShlinkApiClient');
|
||||
};
|
||||
|
||||
export default provideServices;
|
|
@ -1,6 +1,4 @@
|
|||
import { curry } from 'ramda';
|
||||
import PropTypes from 'prop-types';
|
||||
import { buildShlinkApiClientWithAxios as buildShlinkApiClient } from '../../api/ShlinkApiClientBuilder';
|
||||
import { shortUrlType } from '../../short-urls/reducers/shortUrlsList';
|
||||
|
||||
/* eslint-disable padding-line-between-statements, newline-after-var */
|
||||
|
@ -45,7 +43,7 @@ export default function reducer(state = initialState, action) {
|
|||
}
|
||||
}
|
||||
|
||||
export const _getShortUrlDetail = (buildShlinkApiClient, shortCode) => async (dispatch, getState) => {
|
||||
export const getShortUrlDetail = (buildShlinkApiClient) => (shortCode) => async (dispatch, getState) => {
|
||||
dispatch({ type: GET_SHORT_URL_DETAIL_START });
|
||||
|
||||
const { selectedServer } = getState();
|
||||
|
@ -59,5 +57,3 @@ export const _getShortUrlDetail = (buildShlinkApiClient, shortCode) => async (di
|
|||
dispatch({ type: GET_SHORT_URL_DETAIL_ERROR });
|
||||
}
|
||||
};
|
||||
|
||||
export const getShortUrlDetail = curry(_getShortUrlDetail)(buildShlinkApiClient);
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import { curry } from 'ramda';
|
||||
import PropTypes from 'prop-types';
|
||||
import { buildShlinkApiClientWithAxios as buildShlinkApiClient } from '../../api/ShlinkApiClientBuilder';
|
||||
|
||||
/* eslint-disable padding-line-between-statements, newline-after-var */
|
||||
export const GET_SHORT_URL_VISITS_START = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_START';
|
||||
|
@ -44,7 +42,7 @@ export default function reducer(state = initialState, action) {
|
|||
}
|
||||
}
|
||||
|
||||
export const _getShortUrlVisits = (buildShlinkApiClient, shortCode, dates) => async (dispatch, getState) => {
|
||||
export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, dates) => async (dispatch, getState) => {
|
||||
dispatch({ type: GET_SHORT_URL_VISITS_START });
|
||||
|
||||
const { selectedServer } = getState();
|
||||
|
@ -58,5 +56,3 @@ export const _getShortUrlVisits = (buildShlinkApiClient, shortCode, dates) => as
|
|||
dispatch({ type: GET_SHORT_URL_VISITS_ERROR });
|
||||
}
|
||||
};
|
||||
|
||||
export const getShortUrlVisits = curry(_getShortUrlVisits)(buildShlinkApiClient);
|
||||
|
|
|
@ -3,7 +3,7 @@ import { shallow } from 'enzyme';
|
|||
import { identity } from 'ramda';
|
||||
import { Card } from 'reactstrap';
|
||||
import * as sinon from 'sinon';
|
||||
import { ShortUrlsVisitsComponent as ShortUrlsVisits } from '../../src/visits/ShortUrlVisits';
|
||||
import createShortUrlVisits from '../../src/visits/ShortUrlVisits';
|
||||
import MutedMessage from '../../src/utils/MuttedMessage';
|
||||
import GraphCard from '../../src/visits/GraphCard';
|
||||
import DateInput from '../../src/utils/DateInput';
|
||||
|
@ -18,14 +18,17 @@ describe('<ShortUrlVisits />', () => {
|
|||
};
|
||||
|
||||
const createComponent = (shortUrlVisits) => {
|
||||
const ShortUrlVisits = createShortUrlVisits({
|
||||
processBrowserStats: statsProcessor,
|
||||
processCountriesStats: statsProcessor,
|
||||
processOsStats: statsProcessor,
|
||||
processReferrersStats: statsProcessor,
|
||||
});
|
||||
|
||||
wrapper = shallow(
|
||||
<ShortUrlsVisits
|
||||
<ShortUrlVisits
|
||||
getShortUrlDetail={identity}
|
||||
getShortUrlVisits={getShortUrlVisitsMock}
|
||||
processBrowserStats={statsProcessor}
|
||||
processCountriesStats={statsProcessor}
|
||||
processOsStats={statsProcessor}
|
||||
processReferrersStats={statsProcessor}
|
||||
match={match}
|
||||
shortUrlVisits={shortUrlVisits}
|
||||
shortUrlDetail={{}}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as sinon from 'sinon';
|
||||
import reducer, {
|
||||
_getShortUrlDetail,
|
||||
getShortUrlDetail,
|
||||
GET_SHORT_URL_DETAIL_START,
|
||||
GET_SHORT_URL_DETAIL_ERROR,
|
||||
GET_SHORT_URL_DETAIL,
|
||||
|
@ -58,7 +58,7 @@ describe('shortUrlDetailReducer', () => {
|
|||
const ShlinkApiClient = buildApiClientMock(Promise.reject());
|
||||
const expectedDispatchCalls = 2;
|
||||
|
||||
await _getShortUrlDetail(() => ShlinkApiClient, 'abc123')(dispatchMock, getState);
|
||||
await getShortUrlDetail(() => ShlinkApiClient)('abc123')(dispatchMock, getState);
|
||||
|
||||
const [ firstCallArg ] = dispatchMock.getCall(0).args;
|
||||
const { type: firstCallType } = firstCallArg;
|
||||
|
@ -77,7 +77,7 @@ describe('shortUrlDetailReducer', () => {
|
|||
const ShlinkApiClient = buildApiClientMock(Promise.resolve(resolvedShortUrl));
|
||||
const expectedDispatchCalls = 2;
|
||||
|
||||
await _getShortUrlDetail(() => ShlinkApiClient, 'abc123')(dispatchMock, getState);
|
||||
await getShortUrlDetail(() => ShlinkApiClient)('abc123')(dispatchMock, getState);
|
||||
|
||||
const [ firstCallArg ] = dispatchMock.getCall(0).args;
|
||||
const { type: firstCallType } = firstCallArg;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as sinon from 'sinon';
|
||||
import reducer, {
|
||||
_getShortUrlVisits,
|
||||
getShortUrlVisits,
|
||||
GET_SHORT_URL_VISITS_START,
|
||||
GET_SHORT_URL_VISITS_ERROR,
|
||||
GET_SHORT_URL_VISITS,
|
||||
|
@ -58,7 +58,7 @@ describe('shortUrlVisitsReducer', () => {
|
|||
const ShlinkApiClient = buildApiClientMock(Promise.reject());
|
||||
const expectedDispatchCalls = 2;
|
||||
|
||||
await _getShortUrlVisits(() => ShlinkApiClient, 'abc123')(dispatchMock, getState);
|
||||
await getShortUrlVisits(() => ShlinkApiClient)('abc123')(dispatchMock, getState);
|
||||
|
||||
const [ firstCallArg ] = dispatchMock.getCall(0).args;
|
||||
const { type: firstCallType } = firstCallArg;
|
||||
|
@ -77,7 +77,7 @@ describe('shortUrlVisitsReducer', () => {
|
|||
const ShlinkApiClient = buildApiClientMock(Promise.resolve(resolvedVisits));
|
||||
const expectedDispatchCalls = 2;
|
||||
|
||||
await _getShortUrlVisits(() => ShlinkApiClient, 'abc123')(dispatchMock, getState);
|
||||
await getShortUrlVisits(() => ShlinkApiClient)('abc123')(dispatchMock, getState);
|
||||
|
||||
const [ firstCallArg ] = dispatchMock.getCall(0).args;
|
||||
const { type: firstCallType } = firstCallArg;
|
||||
|
|
Loading…
Reference in a new issue