mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 17:40:23 +03:00
Merge pull request #91 from acelaya/feature/cities-graph
Feature/cities graph
This commit is contained in:
commit
95220b913a
5 changed files with 43 additions and 13 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue