mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 17:40:23 +03:00
Merge pull request #214 from acelaya-forks/feature/consistent-server-loading
Feature/consistent server loading
This commit is contained in:
commit
451c77d47f
25 changed files with 129 additions and 166 deletions
|
@ -1,53 +1,47 @@
|
||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Route, Switch } from 'react-router-dom';
|
import { Route, Switch } from 'react-router-dom';
|
||||||
import { Swipeable } from 'react-swipeable';
|
import { Swipeable } from 'react-swipeable';
|
||||||
import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons';
|
import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import * as PropTypes from 'prop-types';
|
import * as PropTypes from 'prop-types';
|
||||||
import { serverType } from '../servers/prop-types';
|
import { serverType } from '../servers/prop-types';
|
||||||
|
import MutedMessage from '../utils/MutedMessage';
|
||||||
import NotFound from './NotFound';
|
import NotFound from './NotFound';
|
||||||
import './MenuLayout.scss';
|
import './MenuLayout.scss';
|
||||||
|
|
||||||
const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisits) =>
|
const propTypes = {
|
||||||
class MenuLayout extends React.Component {
|
|
||||||
static propTypes = {
|
|
||||||
match: PropTypes.object,
|
match: PropTypes.object,
|
||||||
selectServer: PropTypes.func,
|
selectServer: PropTypes.func,
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
selectedServer: serverType,
|
selectedServer: serverType,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = { showSideBar: false };
|
const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisits) => {
|
||||||
|
const MenuLayoutComp = ({ match, location, selectedServer, selectServer }) => {
|
||||||
|
const [ showSideBar, setShowSidebar ] = useState(false);
|
||||||
|
|
||||||
componentDidMount() {
|
useEffect(() => {
|
||||||
const { match, selectServer } = this.props;
|
|
||||||
const { params: { serverId } } = match;
|
const { params: { serverId } } = match;
|
||||||
|
|
||||||
selectServer(serverId);
|
selectServer(serverId);
|
||||||
|
}, []);
|
||||||
|
useEffect(() => setShowSidebar(false), [ location ]);
|
||||||
|
|
||||||
|
if (!selectedServer) {
|
||||||
|
return <MutedMessage loading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
const { location } = this.props;
|
|
||||||
|
|
||||||
// Hide sidebar when location changes
|
|
||||||
if (location !== prevProps.location) {
|
|
||||||
this.setState({ showSideBar: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { selectedServer, match } = this.props;
|
|
||||||
const { params: { serverId } } = match;
|
const { params: { serverId } } = match;
|
||||||
const burgerClasses = classnames('menu-layout__burger-icon', {
|
const burgerClasses = classNames('menu-layout__burger-icon', {
|
||||||
'menu-layout__burger-icon--active': this.state.showSideBar,
|
'menu-layout__burger-icon--active': showSideBar,
|
||||||
});
|
});
|
||||||
const swipeMenuIfNoModalExists = (showSideBar) => () => {
|
const swipeMenuIfNoModalExists = (showSideBar) => () => {
|
||||||
if (document.querySelector('.modal')) {
|
if (document.querySelector('.modal')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ showSideBar });
|
setShowSidebar(showSideBar);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -55,7 +49,7 @@ const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisi
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={burgerIcon}
|
icon={burgerIcon}
|
||||||
className={burgerClasses}
|
className={burgerClasses}
|
||||||
onClick={() => this.setState(({ showSideBar }) => ({ showSideBar: !showSideBar }))}
|
onClick={() => setShowSidebar(!showSideBar)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Swipeable
|
<Swipeable
|
||||||
|
@ -65,36 +59,13 @@ const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisi
|
||||||
onSwipedRight={swipeMenuIfNoModalExists(true)}
|
onSwipedRight={swipeMenuIfNoModalExists(true)}
|
||||||
>
|
>
|
||||||
<div className="row menu-layout__swipeable-inner">
|
<div className="row menu-layout__swipeable-inner">
|
||||||
<AsideMenu
|
<AsideMenu className="col-lg-2 col-md-3" selectedServer={selectedServer} showOnMobile={showSideBar} />
|
||||||
className="col-lg-2 col-md-3"
|
<div className="col-lg-10 offset-lg-2 col-md-9 offset-md-3" onClick={() => setShowSidebar(false)}>
|
||||||
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>
|
<Switch>
|
||||||
<Route
|
<Route exact path="/server/:serverId/list-short-urls/:page" component={ShortUrls} />
|
||||||
exact
|
<Route exact path="/server/:serverId/create-short-url" component={CreateShortUrl} />
|
||||||
path="/server/:serverId/list-short-urls/:page"
|
<Route exact path="/server/:serverId/short-code/:shortCode/visits" component={ShortUrlVisits} />
|
||||||
component={ShortUrls}
|
<Route exact path="/server/:serverId/manage-tags" component={TagsList} />
|
||||||
/>
|
|
||||||
<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}
|
|
||||||
/>
|
|
||||||
<Route
|
<Route
|
||||||
render={() => <NotFound to={`/server/${serverId}/list-short-urls/1`} btnText="List short URLs" />}
|
render={() => <NotFound to={`/server/${serverId}/list-short-urls/1`} btnText="List short URLs" />}
|
||||||
/>
|
/>
|
||||||
|
@ -104,7 +75,11 @@ const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisi
|
||||||
</Swipeable>
|
</Swipeable>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MenuLayoutComp.propTypes = propTypes;
|
||||||
|
|
||||||
|
return MenuLayoutComp;
|
||||||
|
};
|
||||||
|
|
||||||
export default MenuLayout;
|
export default MenuLayout;
|
||||||
|
|
|
@ -19,7 +19,7 @@ export const selectServer = ({ findServerById }, buildShlinkApiClient) => (serve
|
||||||
dispatch(resetShortUrlParams());
|
dispatch(resetShortUrlParams());
|
||||||
|
|
||||||
const selectedServer = findServerById(serverId);
|
const selectedServer = findServerById(serverId);
|
||||||
const { health } = await buildShlinkApiClient(selectedServer);
|
const { health } = buildShlinkApiClient(selectedServer);
|
||||||
const version = await health()
|
const version = await health()
|
||||||
.then(({ version }) => version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version)
|
.then(({ version }) => version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version)
|
||||||
.then((version) => !versionIsValidSemVer(version) ? MIN_FALLBACK_VERSION : version)
|
.then((version) => !versionIsValidSemVer(version) ? MIN_FALLBACK_VERSION : version)
|
||||||
|
|
|
@ -31,8 +31,7 @@ export default handleActions({
|
||||||
|
|
||||||
export const createShortUrl = (buildShlinkApiClient) => (data) => async (dispatch, getState) => {
|
export const createShortUrl = (buildShlinkApiClient) => (data) => async (dispatch, getState) => {
|
||||||
dispatch({ type: CREATE_SHORT_URL_START });
|
dispatch({ type: CREATE_SHORT_URL_START });
|
||||||
|
const { createShortUrl } = buildShlinkApiClient(getState);
|
||||||
const { createShortUrl } = await buildShlinkApiClient(getState);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await createShortUrl(data);
|
const result = await createShortUrl(data);
|
||||||
|
|
|
@ -32,8 +32,7 @@ export default handleActions({
|
||||||
|
|
||||||
export const deleteShortUrl = (buildShlinkApiClient) => (shortCode, domain) => async (dispatch, getState) => {
|
export const deleteShortUrl = (buildShlinkApiClient) => (shortCode, domain) => async (dispatch, getState) => {
|
||||||
dispatch({ type: DELETE_SHORT_URL_START });
|
dispatch({ type: DELETE_SHORT_URL_START });
|
||||||
|
const { deleteShortUrl } = buildShlinkApiClient(getState);
|
||||||
const { deleteShortUrl } = await buildShlinkApiClient(getState);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await deleteShortUrl(shortCode, domain);
|
await deleteShortUrl(shortCode, domain);
|
||||||
|
|
|
@ -37,7 +37,7 @@ export default handleActions({
|
||||||
|
|
||||||
export const editShortUrlMeta = (buildShlinkApiClient) => (shortCode, domain, meta) => async (dispatch, getState) => {
|
export const editShortUrlMeta = (buildShlinkApiClient) => (shortCode, domain, meta) => async (dispatch, getState) => {
|
||||||
dispatch({ type: EDIT_SHORT_URL_META_START });
|
dispatch({ type: EDIT_SHORT_URL_META_START });
|
||||||
const { updateShortUrlMeta } = await buildShlinkApiClient(getState);
|
const { updateShortUrlMeta } = buildShlinkApiClient(getState);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateShortUrlMeta(shortCode, domain, meta);
|
await updateShortUrlMeta(shortCode, domain, meta);
|
||||||
|
|
|
@ -31,7 +31,7 @@ export default handleActions({
|
||||||
|
|
||||||
export const editShortUrlTags = (buildShlinkApiClient) => (shortCode, domain, tags) => async (dispatch, getState) => {
|
export const editShortUrlTags = (buildShlinkApiClient) => (shortCode, domain, tags) => async (dispatch, getState) => {
|
||||||
dispatch({ type: EDIT_SHORT_URL_TAGS_START });
|
dispatch({ type: EDIT_SHORT_URL_TAGS_START });
|
||||||
const { updateShortUrlTags } = await buildShlinkApiClient(getState);
|
const { updateShortUrlTags } = buildShlinkApiClient(getState);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const normalizedTags = await updateShortUrlTags(shortCode, domain, tags);
|
const normalizedTags = await updateShortUrlTags(shortCode, domain, tags);
|
||||||
|
|
|
@ -58,8 +58,7 @@ export default handleActions({
|
||||||
|
|
||||||
export const listShortUrls = (buildShlinkApiClient) => (params = {}) => async (dispatch, getState) => {
|
export const listShortUrls = (buildShlinkApiClient) => (params = {}) => async (dispatch, getState) => {
|
||||||
dispatch({ type: LIST_SHORT_URLS_START });
|
dispatch({ type: LIST_SHORT_URLS_START });
|
||||||
|
const { listShortUrls } = buildShlinkApiClient(getState);
|
||||||
const { listShortUrls } = await buildShlinkApiClient(getState);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shortUrls = await listShortUrls(params);
|
const shortUrls = await listShortUrls(params);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { splitEvery } from 'ramda';
|
import { splitEvery } from 'ramda';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import MuttedMessage from '../utils/MuttedMessage';
|
import MutedMessage from '../utils/MutedMessage';
|
||||||
import SearchField from '../utils/SearchField';
|
import SearchField from '../utils/SearchField';
|
||||||
|
|
||||||
const { ceil } = Math;
|
const { ceil } = Math;
|
||||||
|
@ -29,7 +29,7 @@ const TagsList = (TagCard) => class TagsList extends React.Component {
|
||||||
const { tagsList, match } = this.props;
|
const { tagsList, match } = this.props;
|
||||||
|
|
||||||
if (tagsList.loading) {
|
if (tagsList.loading) {
|
||||||
return <MuttedMessage marginSize={0}>Loading...</MuttedMessage>;
|
return <MutedMessage noMargin loading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tagsList.error) {
|
if (tagsList.error) {
|
||||||
|
@ -43,7 +43,7 @@ const TagsList = (TagCard) => class TagsList extends React.Component {
|
||||||
const tagsCount = tagsList.filteredTags.length;
|
const tagsCount = tagsList.filteredTags.length;
|
||||||
|
|
||||||
if (tagsCount < 1) {
|
if (tagsCount < 1) {
|
||||||
return <MuttedMessage>No tags found</MuttedMessage>;
|
return <MutedMessage>No tags found</MutedMessage>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagsGroups = splitEvery(ceil(tagsCount / TAGS_GROUPS_AMOUNT), tagsList.filteredTags);
|
const tagsGroups = splitEvery(ceil(tagsCount / TAGS_GROUPS_AMOUNT), tagsList.filteredTags);
|
||||||
|
|
|
@ -26,8 +26,7 @@ export default handleActions({
|
||||||
|
|
||||||
export const deleteTag = (buildShlinkApiClient) => (tag) => async (dispatch, getState) => {
|
export const deleteTag = (buildShlinkApiClient) => (tag) => async (dispatch, getState) => {
|
||||||
dispatch({ type: DELETE_TAG_START });
|
dispatch({ type: DELETE_TAG_START });
|
||||||
|
const { deleteTags } = buildShlinkApiClient(getState);
|
||||||
const { deleteTags } = await buildShlinkApiClient(getState);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await deleteTags([ tag ]);
|
await deleteTags([ tag ]);
|
||||||
|
|
|
@ -31,8 +31,7 @@ export const editTag = (buildShlinkApiClient, colorGenerator) => (oldName, newNa
|
||||||
getState
|
getState
|
||||||
) => {
|
) => {
|
||||||
dispatch({ type: EDIT_TAG_START });
|
dispatch({ type: EDIT_TAG_START });
|
||||||
|
const { editTag } = buildShlinkApiClient(getState);
|
||||||
const { editTag } = await buildShlinkApiClient(getState);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await editTag(oldName, newName);
|
await editTag(oldName, newName);
|
||||||
|
|
|
@ -50,7 +50,7 @@ export const listTags = (buildShlinkApiClient, force = true) => () => async (dis
|
||||||
dispatch({ type: LIST_TAGS_START });
|
dispatch({ type: LIST_TAGS_START });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { listTags } = await buildShlinkApiClient(getState);
|
const { listTags } = buildShlinkApiClient(getState);
|
||||||
const tags = await listTags();
|
const tags = await listTags();
|
||||||
|
|
||||||
dispatch({ tags, type: LIST_TAGS });
|
dispatch({ tags, type: LIST_TAGS });
|
||||||
|
|
34
src/utils/MutedMessage.js
Normal file
34
src/utils/MutedMessage.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Card } from 'reactstrap';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { faCircleNotch as preloader } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
noMargin: PropTypes.bool,
|
||||||
|
loading: PropTypes.bool,
|
||||||
|
children: PropTypes.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
const MutedMessage = ({ children, loading = false, noMargin = false }) => {
|
||||||
|
const cardClasses = classNames('bg-light', {
|
||||||
|
'mt-4': !noMargin,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-md-10 offset-md-1">
|
||||||
|
<Card className={cardClasses} body>
|
||||||
|
<h3 className="text-center text-muted mb-0">
|
||||||
|
{loading && <FontAwesomeIcon icon={preloader} spin />}
|
||||||
|
{loading && !children && <span className="ml-2">Loading...</span>}
|
||||||
|
{children}
|
||||||
|
</h3>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
MutedMessage.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default MutedMessage;
|
|
@ -1,28 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Card } from 'reactstrap';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
const DEFAULT_MARGIN_SIZE = 4;
|
|
||||||
const propTypes = {
|
|
||||||
marginSize: PropTypes.number,
|
|
||||||
children: PropTypes.node,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function MutedMessage({ children, marginSize = DEFAULT_MARGIN_SIZE }) {
|
|
||||||
const cardClasses = classnames('bg-light', {
|
|
||||||
[`mt-${marginSize}`]: marginSize > 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="col-md-10 offset-md-1">
|
|
||||||
<Card className={cardClasses} body>
|
|
||||||
<h3 className="text-center text-muted mb-0">
|
|
||||||
{children}
|
|
||||||
</h3>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
MutedMessage.propTypes = propTypes;
|
|
|
@ -1,21 +1,16 @@
|
||||||
import { wait } from '../utils';
|
|
||||||
import ShlinkApiClient from './ShlinkApiClient';
|
import ShlinkApiClient from './ShlinkApiClient';
|
||||||
|
|
||||||
const apiClients = {};
|
const apiClients = {};
|
||||||
|
|
||||||
const getSelectedServerFromState = async (getState) => {
|
const getSelectedServerFromState = (getState) => {
|
||||||
const { selectedServer } = getState();
|
const { selectedServer } = getState();
|
||||||
|
|
||||||
if (!selectedServer) {
|
|
||||||
return wait(250).then(() => getSelectedServerFromState(getState));
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectedServer;
|
return selectedServer;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildShlinkApiClient = (axios) => async (getStateOrSelectedServer) => {
|
const buildShlinkApiClient = (axios) => (getStateOrSelectedServer) => {
|
||||||
const { url, apiKey } = typeof getStateOrSelectedServer === 'function'
|
const { url, apiKey } = typeof getStateOrSelectedServer === 'function'
|
||||||
? await getSelectedServerFromState(getStateOrSelectedServer)
|
? getSelectedServerFromState(getStateOrSelectedServer)
|
||||||
: getStateOrSelectedServer;
|
: getStateOrSelectedServer;
|
||||||
const clientKey = `${url}_${apiKey}`;
|
const clientKey = `${url}_${apiKey}`;
|
||||||
|
|
||||||
|
|
|
@ -53,8 +53,6 @@ export const useToggle = (initialValue = false) => {
|
||||||
return [ flag, () => setFlag(!flag) ];
|
return [ flag, () => setFlag(!flag) ];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const wait = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
||||||
|
|
||||||
export const compareVersions = (firstVersion, operator, secondVersion) => compare(
|
export const compareVersions = (firstVersion, operator, secondVersion) => compare(
|
||||||
firstVersion,
|
firstVersion,
|
||||||
secondVersion,
|
secondVersion,
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import { faCircleNotch as preloader } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { isEmpty, mapObjIndexed, values } from 'ramda';
|
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 qs from 'qs';
|
||||||
import DateRangeRow from '../utils/DateRangeRow';
|
import DateRangeRow from '../utils/DateRangeRow';
|
||||||
import MutedMessage from '../utils/MuttedMessage';
|
import MutedMessage from '../utils/MutedMessage';
|
||||||
import { formatDate } from '../utils/utils';
|
import { formatDate } from '../utils/utils';
|
||||||
import SortableBarGraph from './SortableBarGraph';
|
import SortableBarGraph from './SortableBarGraph';
|
||||||
import { shortUrlVisitsType } from './reducers/shortUrlVisits';
|
import { shortUrlVisitsType } from './reducers/shortUrlVisits';
|
||||||
|
@ -66,7 +64,7 @@ const ShortUrlVisits = (
|
||||||
if (loading) {
|
if (loading) {
|
||||||
const message = loadingLarge ? 'This is going to take a while... :S' : 'Loading...';
|
const message = loadingLarge ? 'This is going to take a while... :S' : 'Loading...';
|
||||||
|
|
||||||
return <MutedMessage><FontAwesomeIcon icon={preloader} spin /> {message}</MutedMessage>;
|
return <MutedMessage loading>{message}</MutedMessage>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|
|
@ -28,8 +28,7 @@ export default handleActions({
|
||||||
|
|
||||||
export const getShortUrlDetail = (buildShlinkApiClient) => (shortCode, domain) => async (dispatch, getState) => {
|
export const getShortUrlDetail = (buildShlinkApiClient) => (shortCode, domain) => async (dispatch, getState) => {
|
||||||
dispatch({ type: GET_SHORT_URL_DETAIL_START });
|
dispatch({ type: GET_SHORT_URL_DETAIL_START });
|
||||||
|
const { getShortUrl } = buildShlinkApiClient(getState);
|
||||||
const { getShortUrl } = await buildShlinkApiClient(getState);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shortUrl = await getShortUrl(shortCode, domain);
|
const shortUrl = await getShortUrl(shortCode, domain);
|
||||||
|
|
|
@ -51,8 +51,7 @@ export default handleActions({
|
||||||
|
|
||||||
export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, query) => 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 } = buildShlinkApiClient(getState);
|
||||||
const { getShortUrlVisits } = await buildShlinkApiClient(getState);
|
|
||||||
const itemsPerPage = 5000;
|
const itemsPerPage = 5000;
|
||||||
const isLastPage = ({ currentPage, pagesCount }) => currentPage >= pagesCount;
|
const isLastPage = ({ currentPage, pagesCount }) => currentPage >= pagesCount;
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ describe('selectedServerReducer', () => {
|
||||||
const apiClientMock = {
|
const apiClientMock = {
|
||||||
health: jest.fn(),
|
health: jest.fn(),
|
||||||
};
|
};
|
||||||
const buildApiClient = jest.fn().mockResolvedValue(apiClientMock);
|
const buildApiClient = jest.fn().mockReturnValue(apiClientMock);
|
||||||
const dispatch = jest.fn();
|
const dispatch = jest.fn();
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
afterEach(jest.clearAllMocks);
|
||||||
|
|
|
@ -51,7 +51,7 @@ describe('shortUrlMetaReducer', () => {
|
||||||
|
|
||||||
describe('editShortUrlMeta', () => {
|
describe('editShortUrlMeta', () => {
|
||||||
const updateShortUrlMeta = jest.fn().mockResolvedValue({});
|
const updateShortUrlMeta = jest.fn().mockResolvedValue({});
|
||||||
const buildShlinkApiClient = jest.fn().mockResolvedValue({ updateShortUrlMeta });
|
const buildShlinkApiClient = jest.fn().mockReturnValue({ updateShortUrlMeta });
|
||||||
const dispatch = jest.fn();
|
const dispatch = jest.fn();
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
afterEach(jest.clearAllMocks);
|
||||||
|
|
|
@ -51,14 +51,10 @@ describe('shortUrlTagsReducer', () => {
|
||||||
|
|
||||||
describe('editShortUrlTags', () => {
|
describe('editShortUrlTags', () => {
|
||||||
const updateShortUrlTags = jest.fn();
|
const updateShortUrlTags = jest.fn();
|
||||||
const buildShlinkApiClient = jest.fn().mockResolvedValue({ updateShortUrlTags });
|
const buildShlinkApiClient = jest.fn().mockReturnValue({ updateShortUrlTags });
|
||||||
const dispatch = jest.fn();
|
const dispatch = jest.fn();
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(jest.clearAllMocks);
|
||||||
updateShortUrlTags.mockReset();
|
|
||||||
buildShlinkApiClient.mockClear();
|
|
||||||
dispatch.mockReset();
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each([[ undefined ], [ null ], [ 'example.com' ]])('dispatches normalized tags on success', async (domain) => {
|
it.each([[ undefined ], [ null ], [ 'example.com' ]])('dispatches normalized tags on success', async (domain) => {
|
||||||
const normalizedTags = [ 'bar', 'foo' ];
|
const normalizedTags = [ 'bar', 'foo' ];
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import { identity } from 'ramda';
|
import { identity } from 'ramda';
|
||||||
import createTagsList from '../../src/tags/TagsList';
|
import createTagsList from '../../src/tags/TagsList';
|
||||||
import MuttedMessage from '../../src/utils/MuttedMessage';
|
import MutedMessage from '../../src/utils/MutedMessage';
|
||||||
import SearchField from '../../src/utils/SearchField';
|
import SearchField from '../../src/utils/SearchField';
|
||||||
import { rangeOf } from '../../src/utils/utils';
|
import { rangeOf } from '../../src/utils/utils';
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ describe('<TagsList />', () => {
|
||||||
|
|
||||||
it('shows a loading message when tags are being loaded', () => {
|
it('shows a loading message when tags are being loaded', () => {
|
||||||
const wrapper = createWrapper({ loading: true });
|
const wrapper = createWrapper({ loading: true });
|
||||||
const loadingMsg = wrapper.find(MuttedMessage);
|
const loadingMsg = wrapper.find(MutedMessage);
|
||||||
|
|
||||||
expect(loadingMsg).toHaveLength(1);
|
expect(loadingMsg).toHaveLength(1);
|
||||||
expect(loadingMsg.html()).toContain('Loading...');
|
expect(loadingMsg.html()).toContain('Loading...');
|
||||||
|
@ -44,7 +44,7 @@ describe('<TagsList />', () => {
|
||||||
|
|
||||||
it('shows a message when the list of tags is empty', () => {
|
it('shows a message when the list of tags is empty', () => {
|
||||||
const wrapper = createWrapper({ filteredTags: [] });
|
const wrapper = createWrapper({ filteredTags: [] });
|
||||||
const msg = wrapper.find(MuttedMessage);
|
const msg = wrapper.find(MutedMessage);
|
||||||
|
|
||||||
expect(msg).toHaveLength(1);
|
expect(msg).toHaveLength(1);
|
||||||
expect(msg.html()).toContain('No tags found');
|
expect(msg.html()).toContain('No tags found');
|
||||||
|
|
|
@ -104,7 +104,7 @@ describe('tagsListReducer', () => {
|
||||||
const tags = [ 'foo', 'bar', 'baz' ];
|
const tags = [ 'foo', 'bar', 'baz' ];
|
||||||
|
|
||||||
listTagsMock.mockResolvedValue(tags);
|
listTagsMock.mockResolvedValue(tags);
|
||||||
buildShlinkApiClient.mockResolvedValue({ listTags: listTagsMock });
|
buildShlinkApiClient.mockReturnValue({ listTags: listTagsMock });
|
||||||
|
|
||||||
await listTags(buildShlinkApiClient, true)()(dispatch, getState);
|
await listTags(buildShlinkApiClient, true)()(dispatch, getState);
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ describe('tagsListReducer', () => {
|
||||||
|
|
||||||
it('dispatches error when error occurs on list call', async () => {
|
it('dispatches error when error occurs on list call', async () => {
|
||||||
listTagsMock.mockRejectedValue(new Error());
|
listTagsMock.mockRejectedValue(new Error());
|
||||||
buildShlinkApiClient.mockResolvedValue({ listTags: listTagsMock });
|
buildShlinkApiClient.mockReturnValue({ listTags: listTagsMock });
|
||||||
|
|
||||||
await assertErrorResult();
|
await assertErrorResult();
|
||||||
|
|
||||||
|
@ -135,7 +135,9 @@ describe('tagsListReducer', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('dispatches error when error occurs on build call', async () => {
|
it('dispatches error when error occurs on build call', async () => {
|
||||||
buildShlinkApiClient.mockRejectedValue(new Error());
|
buildShlinkApiClient.mockImplementation(() => {
|
||||||
|
throw new Error();
|
||||||
|
});
|
||||||
|
|
||||||
await assertErrorResult();
|
await assertErrorResult();
|
||||||
|
|
||||||
|
|
|
@ -34,10 +34,10 @@ describe('ShlinkApiClientBuilder', () => {
|
||||||
expect(secondApiClient).toBe(thirdApiClient);
|
expect(secondApiClient).toBe(thirdApiClient);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not fetch from state when provided param is already selected server', async () => {
|
it('does not fetch from state when provided param is already selected server', () => {
|
||||||
const url = 'url';
|
const url = 'url';
|
||||||
const apiKey = 'apiKey';
|
const apiKey = 'apiKey';
|
||||||
const apiClient = await buildShlinkApiClient({})({ url, apiKey });
|
const apiClient = buildShlinkApiClient({})({ url, apiKey });
|
||||||
|
|
||||||
expect(apiClient._baseUrl).toEqual(url);
|
expect(apiClient._baseUrl).toEqual(url);
|
||||||
expect(apiClient._apiKey).toEqual(apiKey);
|
expect(apiClient._apiKey).toEqual(apiKey);
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { shallow } from 'enzyme';
|
||||||
import { identity } from 'ramda';
|
import { identity } from 'ramda';
|
||||||
import { Card } from 'reactstrap';
|
import { Card } from 'reactstrap';
|
||||||
import createShortUrlVisits from '../../src/visits/ShortUrlVisits';
|
import createShortUrlVisits from '../../src/visits/ShortUrlVisits';
|
||||||
import MutedMessage from '../../src/utils/MuttedMessage';
|
import MutedMessage from '../../src/utils/MutedMessage';
|
||||||
import GraphCard from '../../src/visits/GraphCard';
|
import GraphCard from '../../src/visits/GraphCard';
|
||||||
import SortableBarGraph from '../../src/visits/SortableBarGraph';
|
import SortableBarGraph from '../../src/visits/SortableBarGraph';
|
||||||
import DateRangeRow from '../../src/utils/DateRangeRow';
|
import DateRangeRow from '../../src/utils/DateRangeRow';
|
||||||
|
|
Loading…
Reference in a new issue