diff --git a/src/common/MenuLayout.js b/src/common/MenuLayout.js index fc18676d..70a8e164 100644 --- a/src/common/MenuLayout.js +++ b/src/common/MenuLayout.js @@ -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 ( + + this.setState(({ showSideBar }) => ({ showSideBar: !showSideBar }))} + /> + + this.setState({ showSideBar: false })} + onSwipedRight={() => this.setState({ showSideBar: true })} + > + + + this.setState({ showSideBar: false })} + > + + + + + + + + + + + ); + } }; - 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 ( - - this.setState(({ showSideBar }) => ({ showSideBar: !showSideBar }))} - /> - - this.setState({ showSideBar: false })} - onSwipedRight={() => this.setState({ showSideBar: true })} - > - - - this.setState({ showSideBar: false })} - > - - - - - - - - - - - ); - } -}; - export default MenuLayout; diff --git a/src/container/index.js b/src/container/index.js index 8bd9537e..4aba91ea 100644 --- a/src/container/index.js +++ b/src/container/index.js @@ -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; diff --git a/src/visits/ShortUrlVisits.js b/src/visits/ShortUrlVisits.js index 037157de..35446fd3 100644 --- a/src/visits/ShortUrlVisits.js +++ b/src/visits/ShortUrlVisits.js @@ -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 { ); } -} +}; -const ShortUrlsVisits = connect( - pick([ 'shortUrlVisits', 'shortUrlDetail' ]), - { getShortUrlVisits, getShortUrlDetail } -)(ShortUrlsVisitsComponent); - -export default ShortUrlsVisits; +export default ShortUrlVisits; diff --git a/src/visits/container/provideServices.js b/src/visits/container/provideServices.js new file mode 100644 index 00000000..a7aec43f --- /dev/null +++ b/src/visits/container/provideServices.js @@ -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; diff --git a/src/visits/reducers/shortUrlDetail.js b/src/visits/reducers/shortUrlDetail.js index b62c99ce..385c8071 100644 --- a/src/visits/reducers/shortUrlDetail.js +++ b/src/visits/reducers/shortUrlDetail.js @@ -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); diff --git a/src/visits/reducers/shortUrlVisits.js b/src/visits/reducers/shortUrlVisits.js index 4df1a09c..8bdedc33 100644 --- a/src/visits/reducers/shortUrlVisits.js +++ b/src/visits/reducers/shortUrlVisits.js @@ -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); diff --git a/test/visits/ShortUrlVisits.test.js b/test/visits/ShortUrlVisits.test.js index f103987d..788623cb 100644 --- a/test/visits/ShortUrlVisits.test.js +++ b/test/visits/ShortUrlVisits.test.js @@ -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('', () => { }; const createComponent = (shortUrlVisits) => { + const ShortUrlVisits = createShortUrlVisits({ + processBrowserStats: statsProcessor, + processCountriesStats: statsProcessor, + processOsStats: statsProcessor, + processReferrersStats: statsProcessor, + }); + wrapper = shallow( - { 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; diff --git a/test/visits/reducers/shortUrlVisits.test.js b/test/visits/reducers/shortUrlVisits.test.js index f3e47ff1..48c8dc9d 100644 --- a/test/visits/reducers/shortUrlVisits.test.js +++ b/test/visits/reducers/shortUrlVisits.test.js @@ -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;