Merge pull request #91 from acelaya/feature/cities-graph

Feature/cities graph
This commit is contained in:
Alejandro Celaya 2019-01-07 12:02:11 +01:00 committed by GitHub
commit 95220b913a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 43 additions and 13 deletions

View file

@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
#### Added #### Added
* *Nothing* * [#54](https://github.com/shlinkio/shlink-web-client/issues/54) Added stats by city graphic in visits page.
#### Changed #### Changed

View file

@ -17,6 +17,7 @@ const ShortUrlVisits = ({
processOsStats, processOsStats,
processBrowserStats, processBrowserStats,
processCountriesStats, processCountriesStats,
processCitiesStats,
processReferrersStats, processReferrersStats,
}) => class ShortUrlVisits extends React.Component { }) => class ShortUrlVisits extends React.Component {
static propTypes = { static propTypes = {
@ -70,13 +71,23 @@ const ShortUrlVisits = ({
return ( return (
<div className="row"> <div className="row">
<div className="col-md-6"> <div className="col-xl-4 col-lg-6">
<GraphCard title="Operating systems" stats={processOsStats(visits)} /> <GraphCard title="Operating systems" stats={processOsStats(visits)} />
</div> </div>
<div className="col-md-6"> <div className="col-xl-4 col-lg-6">
<GraphCard title="Browsers" stats={processBrowserStats(visits)} /> <GraphCard title="Browsers" stats={processBrowserStats(visits)} />
</div> </div>
<div className="col-md-6"> <div className="col-xl-4">
<SortableBarGraph
stats={processReferrersStats(visits)}
title="Referrers"
sortingItems={{
name: 'Referrer name',
amount: 'Visits amount',
}}
/>
</div>
<div className="col-lg-6">
<SortableBarGraph <SortableBarGraph
stats={processCountriesStats(visits)} stats={processCountriesStats(visits)}
title="Countries" title="Countries"
@ -86,12 +97,12 @@ const ShortUrlVisits = ({
}} }}
/> />
</div> </div>
<div className="col-md-6"> <div className="col-lg-6">
<SortableBarGraph <SortableBarGraph
stats={processReferrersStats(visits)} stats={processCitiesStats(visits)}
title="Referrers" title="Cities"
sortingItems={{ sortingItems={{
name: 'Referrer name', name: 'City name',
amount: 'Visits amount', amount: 'Visits amount',
}} }}
/> />

View file

@ -76,16 +76,20 @@ export const processReferrersStats = (visits) =>
visits, visits,
); );
export const processCountriesStats = (visits) => const buildLocationStatsProcessorByProperty = (propertyName) => (visits) =>
reduce( reduce(
(stats, { visitLocation }) => { (stats, { visitLocation }) => {
const notHasCountry = isNil(visitLocation) const notHasCountry = isNil(visitLocation)
|| isNil(visitLocation.countryName) || isNil(visitLocation[propertyName])
|| isEmpty(visitLocation.countryName); || isEmpty(visitLocation[propertyName]);
const country = notHasCountry ? 'Unknown' : visitLocation.countryName; const country = notHasCountry ? 'Unknown' : visitLocation[propertyName];
return assoc(country, (stats[country] || 0) + 1, stats); return assoc(country, (stats[country] || 0) + 1, stats);
}, },
{}, {},
visits, visits,
); );
export const processCountriesStats = buildLocationStatsProcessorByProperty('countryName');
export const processCitiesStats = buildLocationStatsProcessorByProperty('cityName');

View file

@ -23,6 +23,7 @@ describe('<ShortUrlVisits />', () => {
processCountriesStats: statsProcessor, processCountriesStats: statsProcessor,
processOsStats: statsProcessor, processOsStats: statsProcessor,
processReferrersStats: statsProcessor, processReferrersStats: statsProcessor,
processCitiesStats: statsProcessor,
}); });
wrapper = shallow( wrapper = shallow(
@ -74,7 +75,7 @@ describe('<ShortUrlVisits />', () => {
const wrapper = createComponent({ loading: false, error: false, visits: [{}, {}, {}] }); const wrapper = createComponent({ loading: false, error: false, visits: [{}, {}, {}] });
const graphs = wrapper.find(GraphCard); const graphs = wrapper.find(GraphCard);
const sortableBarGraphs = wrapper.find(SortableBarGraph); const sortableBarGraphs = wrapper.find(SortableBarGraph);
const expectedGraphsCount = 4; const expectedGraphsCount = 5;
expect(graphs.length + sortableBarGraphs.length).toEqual(expectedGraphsCount); expect(graphs.length + sortableBarGraphs.length).toEqual(expectedGraphsCount);
}); });

View file

@ -3,6 +3,7 @@ import {
processBrowserStats, processBrowserStats,
processReferrersStats, processReferrersStats,
processCountriesStats, processCountriesStats,
processCitiesStats,
} from '../../../src/visits/services/VisitsParser'; } from '../../../src/visits/services/VisitsParser';
describe('VisitsParser', () => { describe('VisitsParser', () => {
@ -12,6 +13,7 @@ describe('VisitsParser', () => {
referer: 'https://google.com', referer: 'https://google.com',
visitLocation: { visitLocation: {
countryName: 'Spain', countryName: 'Spain',
cityName: 'Zaragoza',
}, },
}, },
{ {
@ -19,6 +21,7 @@ describe('VisitsParser', () => {
referer: 'https://google.com', referer: 'https://google.com',
visitLocation: { visitLocation: {
countryName: 'United States', countryName: 'United States',
cityName: 'New York',
}, },
}, },
{ {
@ -32,6 +35,7 @@ describe('VisitsParser', () => {
referer: 'https://m.facebook.com', referer: 'https://m.facebook.com',
visitLocation: { visitLocation: {
countryName: 'Spain', countryName: 'Spain',
cityName: 'Zaragoza',
}, },
}, },
{ {
@ -78,4 +82,14 @@ describe('VisitsParser', () => {
}); });
}); });
}); });
describe('processCitiesStats', () => {
it('properly parses cities stats', () => {
expect(processCitiesStats(visits)).toEqual({
'Zaragoza': 2,
'New York': 1,
'Unknown': 2,
});
});
});
}); });