Defined visit stats page

This commit is contained in:
Alejandro Celaya 2018-07-29 19:25:22 +02:00
parent c02b0e0591
commit a75c7309f7
8 changed files with 87 additions and 31 deletions

View file

@ -1,6 +1,6 @@
import axios from 'axios';
import qs from 'qs';
import { isEmpty, isNil, pipe, reject } from 'ramda';
import { isEmpty, isNil, reject } from 'ramda';
const API_VERSION = '1';
@ -22,11 +22,6 @@ export class ShlinkApiClient {
this._apiKey = apiKey;
};
/**
* Returns the list of short URLs
* @param options
* @returns {Promise<Array>}
*/
listShortUrls = (options = {}) =>
this._performRequest('/short-codes', 'GET', options)
.then(resp => resp.data.shortUrls)
@ -39,6 +34,11 @@ export class ShlinkApiClient {
.catch(e => this._handleAuthError(e, this.listShortUrls, [filteredOptions]));
};
getShortUrlVisits = shortCode =>
this._performRequest(`/short-codes/${shortCode}/visits`, 'GET')
.then(resp => resp.data.visits.data)
.catch(e => this._handleAuthError(e, this.listShortUrls, [shortCode]));
_performRequest = async (url, method = 'GET', params = {}, data = {}) => {
if (isEmpty(this._token)) {
this._token = await this._authenticate();

View file

@ -9,7 +9,8 @@ import AsideMenu from './AsideMenu';
import { pick } from 'ramda';
export class MenuLayout extends React.Component {
componentDidMount() {
// FIXME Shouldn't use componentWillMount, but this code has to be run before children components are rendered
componentWillMount() {
const { serverId } = this.props.match.params;
this.props.selectServer(serverId);
}

View file

@ -2,7 +2,7 @@ import searchIcon from '@fortawesome/fontawesome-free-solid/faSearch';
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
import React from 'react';
import { connect } from 'react-redux';
import { updateShortUrlsList } from './reducers/shortUrlsList';
import { listShortUrls } from './reducers/shortUrlsList';
import './SearchBar.scss';
import { pick } from 'ramda';
@ -47,10 +47,10 @@ export class SearchBar extends React.Component {
resetTimer();
this.timer = setTimeout(() => {
this.props.updateShortUrlsList({ ...this.props.shortUrlsListParams, searchTerm });
this.props.listShortUrls({ ...this.props.shortUrlsListParams, searchTerm });
resetTimer();
}, 500);
}
}
export default connect(pick(['shortUrlsListParams']), { updateShortUrlsList })(SearchBar);
export default connect(pick(['shortUrlsListParams']), { listShortUrls })(SearchBar);

View file

@ -1,11 +1,32 @@
import React from 'react';
import { connect } from 'react-redux';
import { pick } from 'ramda';
import { getShortUrlVisits } from './reducers/shortUrlVisits';
export class ShortUrlsVisits extends React.Component {
render() {
componentDidMount() {
const { match: { params } } = this.props;
return <div>Visits for <b>{params.shortCode}</b></div>;
this.props.getShortUrlVisits(params.shortCode);
}
render() {
const { match: { params }, selectedServer } = this.props;
const serverUrl = selectedServer ? selectedServer.url : '';
const shortUrl = `${serverUrl}/${params.shortCode}`;
return (
<div className="short-urls-container">
<div className="card bg-light">
<div className="card-body">
<h2>Visit stats for <a target="_blank" href={shortUrl}>{shortUrl}</a></h2>
{/* TODO Once Shlink's API allows it, add total visits counter, long URL and creation time */}
</div>
</div>
</div>
);
}
}
export default connect()(ShortUrlsVisits);
export default connect(pick(['selectedServer']), {
getShortUrlVisits
})(ShortUrlsVisits);

View file

@ -12,8 +12,8 @@ import { pick } from 'ramda';
export class ShortUrlsList extends React.Component {
refreshList = extraParams => {
const { listShortUrls, shortUrlsListParams, match: { params } } = this.props;
listShortUrls(params.serverId, {
const { listShortUrls, shortUrlsListParams } = this.props;
listShortUrls({
...shortUrlsListParams,
...extraParams
});

View file

@ -0,0 +1,46 @@
import ShlinkApiClient from '../../api/ShlinkApiClient';
const GET_SHORT_URL_VISITS_START = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_START';
const GET_SHORT_URL_VISITS_ERROR = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_ERROR';
const GET_SHORT_URL_VISITS = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS';
const initialState = {
visits: [],
loading: false,
error: false
};
export default function dispatch (state = initialState, action) {
switch (action.type) {
case GET_SHORT_URL_VISITS_START:
return {
...state,
loading: true
};
case GET_SHORT_URL_VISITS_ERROR:
return {
...state,
loading: false,
error: true
};
case GET_SHORT_URL_VISITS:
return {
visits: action.visits,
loading: false,
error: false
};
default:
return state;
}
}
export const getShortUrlVisits = shortCode => async dispatch => {
dispatch({ type: GET_SHORT_URL_VISITS_START });
try {
const visits = await ShlinkApiClient.getShortUrlVisits(shortCode);
dispatch({ visits, type: GET_SHORT_URL_VISITS });
} catch (e) {
dispatch({ type: GET_SHORT_URL_VISITS_ERROR });
}
};

View file

@ -1,10 +1,8 @@
import ShlinkApiClient from '../../api/ShlinkApiClient';
import ServersService from '../../servers/services/ServersService';
export const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START';
export const LIST_SHORT_URLS_ERROR = 'shlink/shortUrlsList/LIST_SHORT_URLS_ERROR';
const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START';
const LIST_SHORT_URLS_ERROR = 'shlink/shortUrlsList/LIST_SHORT_URLS_ERROR';
export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS';
export const UPDATE_SHORT_URLS_LIST = LIST_SHORT_URLS;
const initialState = {
shortUrls: [],
@ -32,16 +30,7 @@ export default function reducer(state = initialState, action) {
}
}
export const listShortUrls = (serverId, params = {}) => {
// FIXME There should be a way to not need this, however, the active server is set when any route is loaded, in an
// FIXME outer component's componentDidMount, which makes it be invoked after this action
const selectedServer = ServersService.findServerById(serverId);
ShlinkApiClient.setConfig(selectedServer);
return updateShortUrlsList(params);
};
export const updateShortUrlsList = (params = {}) => async dispatch => {
export const listShortUrls = (params = {}) => async dispatch => {
dispatch({ type: LIST_SHORT_URLS_START });
try {

View file

@ -1,8 +1,7 @@
import { UPDATE_SHORT_URLS_LIST, LIST_SHORT_URLS } from './shortUrlsList';
import { LIST_SHORT_URLS } from './shortUrlsList';
export default function reducer(state = { page: 1 }, action) {
switch (action.type) {
case UPDATE_SHORT_URLS_LIST:
case LIST_SHORT_URLS:
return { ...state, ...action.params };
default: