mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 17:40:23 +03:00
Merge pull request #247 from acelaya-forks/feature/user-agent-improvements
Feature/user agent improvements
This commit is contained in:
commit
d231ed3ede
9 changed files with 64 additions and 59 deletions
|
@ -40,6 +40,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
#### Fixed
|
#### Fixed
|
||||||
|
|
||||||
* [#243](https://github.com/shlinkio/shlink-web-client/issues/243) Fixed loading state and resetting on short URL creation form.
|
* [#243](https://github.com/shlinkio/shlink-web-client/issues/243) Fixed loading state and resetting on short URL creation form.
|
||||||
|
* [#239](https://github.com/shlinkio/shlink-web-client/issues/239) Fixed how user agents are parsed, reducing false results.
|
||||||
|
|
||||||
|
|
||||||
## 2.3.1 - 2020-02-08
|
## 2.3.1 - 2020-02-08
|
||||||
|
|
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -3460,6 +3460,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/bottlejs/-/bottlejs-1.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/bottlejs/-/bottlejs-1.7.2.tgz",
|
||||||
"integrity": "sha512-voMPQ+g8/4GBiDE5lp43fgfoBn7Q5fZI7k3ye8ErrPoHzuuRS0Gff9lZYeyalh86uz5P304iZ14wNAM+3TZguQ=="
|
"integrity": "sha512-voMPQ+g8/4GBiDE5lp43fgfoBn7Q5fZI7k3ye8ErrPoHzuuRS0Gff9lZYeyalh86uz5P304iZ14wNAM+3TZguQ=="
|
||||||
},
|
},
|
||||||
|
"bowser": {
|
||||||
|
"version": "2.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz",
|
||||||
|
"integrity": "sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA=="
|
||||||
|
},
|
||||||
"boxen": {
|
"boxen": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz",
|
"resolved": "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz",
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
"bootstrap": "^4.3.1",
|
"bootstrap": "^4.3.1",
|
||||||
"bottlejs": "^1.7.2",
|
"bottlejs": "^1.7.2",
|
||||||
|
"bowser": "^2.9.0",
|
||||||
"chart.js": "^2.8.0",
|
"chart.js": "^2.8.0",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"compare-versions": "^3.5.1",
|
"compare-versions": "^3.5.1",
|
||||||
|
|
|
@ -1,51 +1,29 @@
|
||||||
|
import bowser from 'bowser';
|
||||||
import { hasValue } from '../utils';
|
import { hasValue } from '../utils';
|
||||||
|
|
||||||
const DEFAULT = 'Others';
|
const DEFAULT = 'Others';
|
||||||
|
const BROWSERS_WHITELIST = [
|
||||||
|
'Android Browser',
|
||||||
|
'Chrome',
|
||||||
|
'Chromium',
|
||||||
|
'Firefox',
|
||||||
|
'Internet Explorer',
|
||||||
|
'Microsoft Edge',
|
||||||
|
'Opera',
|
||||||
|
'Safari',
|
||||||
|
'Samsung Internet for Android',
|
||||||
|
'Vivaldi',
|
||||||
|
'WeChat',
|
||||||
|
];
|
||||||
|
|
||||||
export const osFromUserAgent = (userAgent) => {
|
export const parseUserAgent = (userAgent) => {
|
||||||
if (!hasValue(userAgent)) {
|
if (!hasValue(userAgent)) {
|
||||||
return DEFAULT;
|
return { browser: DEFAULT, os: DEFAULT };
|
||||||
}
|
}
|
||||||
|
|
||||||
const lowerUserAgent = userAgent.toLowerCase();
|
const { browser: { name: browser }, os: { name: os } } = bowser.parse(userAgent);
|
||||||
|
|
||||||
switch (true) {
|
return { os: os || DEFAULT, browser: browser && BROWSERS_WHITELIST.includes(browser) ? browser : DEFAULT };
|
||||||
case lowerUserAgent.includes('linux'):
|
|
||||||
return 'Linux';
|
|
||||||
case lowerUserAgent.includes('windows'):
|
|
||||||
return 'Windows';
|
|
||||||
case lowerUserAgent.includes('mac'):
|
|
||||||
return 'MacOS';
|
|
||||||
case lowerUserAgent.includes('mobi'):
|
|
||||||
return 'Mobile';
|
|
||||||
default:
|
|
||||||
return DEFAULT;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const browserFromUserAgent = (userAgent) => {
|
|
||||||
if (!hasValue(userAgent)) {
|
|
||||||
return DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lowerUserAgent = userAgent.toLowerCase();
|
|
||||||
|
|
||||||
switch (true) {
|
|
||||||
case lowerUserAgent.includes('opera') || lowerUserAgent.includes('opr'):
|
|
||||||
return 'Opera';
|
|
||||||
case lowerUserAgent.includes('firefox'):
|
|
||||||
return 'Firefox';
|
|
||||||
case lowerUserAgent.includes('chrome'):
|
|
||||||
return 'Chrome';
|
|
||||||
case lowerUserAgent.includes('safari'):
|
|
||||||
return 'Safari';
|
|
||||||
case lowerUserAgent.includes('edg'):
|
|
||||||
return 'Microsoft Edge';
|
|
||||||
case lowerUserAgent.includes('msie'):
|
|
||||||
return 'Internet Explorer';
|
|
||||||
default:
|
|
||||||
return DEFAULT;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const extractDomain = (url) => {
|
export const extractDomain = (url) => {
|
||||||
|
|
|
@ -24,12 +24,16 @@ const generateGraphData = (title, isBarChart, labels, data, highlightedData) =>
|
||||||
data,
|
data,
|
||||||
backgroundColor: isBarChart ? 'rgba(70, 150, 229, 0.4)' : [
|
backgroundColor: isBarChart ? 'rgba(70, 150, 229, 0.4)' : [
|
||||||
'#97BBCD',
|
'#97BBCD',
|
||||||
'#DCDCDC',
|
|
||||||
'#F7464A',
|
'#F7464A',
|
||||||
'#46BFBD',
|
'#46BFBD',
|
||||||
'#FDB45C',
|
'#FDB45C',
|
||||||
'#949FB1',
|
'#949FB1',
|
||||||
'#4D5360',
|
'#57A773',
|
||||||
|
'#414066',
|
||||||
|
'#08B2E3',
|
||||||
|
'#B6C454',
|
||||||
|
'#DCDCDC',
|
||||||
|
'#463730',
|
||||||
],
|
],
|
||||||
borderColor: isBarChart ? 'rgba(70, 150, 229, 1)' : 'white',
|
borderColor: isBarChart ? 'rgba(70, 150, 229, 1)' : 'white',
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
|
@ -47,6 +51,14 @@ const generateGraphData = (title, isBarChart, labels, data, highlightedData) =>
|
||||||
|
|
||||||
const dropLabelIfHidden = (label) => label.startsWith('hidden') ? '' : label;
|
const dropLabelIfHidden = (label) => label.startsWith('hidden') ? '' : label;
|
||||||
|
|
||||||
|
const determineHeight = (isBarChart, labels) => {
|
||||||
|
if (!isBarChart && labels.length > 8) {
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isBarChart && labels.length > 20 ? labels.length * 8 : null;
|
||||||
|
};
|
||||||
|
|
||||||
const renderGraph = (title, isBarChart, stats, max, highlightedStats, onClick) => {
|
const renderGraph = (title, isBarChart, stats, max, highlightedStats, onClick) => {
|
||||||
const hasHighlightedStats = highlightedStats && Object.keys(highlightedStats).length > 0;
|
const hasHighlightedStats = highlightedStats && Object.keys(highlightedStats).length > 0;
|
||||||
const Component = isBarChart ? HorizontalBar : Doughnut;
|
const Component = isBarChart ? HorizontalBar : Doughnut;
|
||||||
|
@ -84,7 +96,7 @@ const renderGraph = (title, isBarChart, stats, max, highlightedStats, onClick) =
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
const graphData = generateGraphData(title, isBarChart, labels, data, highlightedData);
|
const graphData = generateGraphData(title, isBarChart, labels, data, highlightedData);
|
||||||
const height = isBarChart && labels.length > 20 ? labels.length * 8 : null;
|
const height = determineHeight(isBarChart, labels);
|
||||||
|
|
||||||
// Provide a key based on the height, so that every time the dataset changes, a new graph is rendered
|
// Provide a key based on the height, so that every time the dataset changes, a new graph is rendered
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -198,7 +198,7 @@ const ShortUrlVisits = ({ processStatsFromVisits, normalizeVisits }, OpenMapModa
|
||||||
</div>
|
</div>
|
||||||
<div className="col-lg-5 col-xl-6 mt-4 mt-lg-0">
|
<div className="col-lg-5 col-xl-6 mt-4 mt-lg-0">
|
||||||
{showTableControls && (
|
{showTableControls && (
|
||||||
<span className={classNames({ 'row flex-row-reverse': isMobileDevice })}>
|
<span className={classNames({ row: isMobileDevice })}>
|
||||||
<span className={classNames({ 'col-6': isMobileDevice })}>
|
<span className={classNames({ 'col-6': isMobileDevice })}>
|
||||||
<Button outline color="primary" block={isMobileDevice} onClick={toggleTable}>
|
<Button outline color="primary" block={isMobileDevice} onClick={toggleTable}>
|
||||||
{showTable ? 'Hide' : 'Show'} table
|
{showTable ? 'Hide' : 'Show'} table
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { isNil, map } from 'ramda';
|
import { isNil, map } from 'ramda';
|
||||||
import { browserFromUserAgent, extractDomain, osFromUserAgent } from '../../utils/helpers/visits';
|
import { extractDomain, parseUserAgent } from '../../utils/helpers/visits';
|
||||||
import { hasValue } from '../../utils/utils';
|
import { hasValue } from '../../utils/utils';
|
||||||
|
|
||||||
const visitHasProperty = (visit, propertyName) => !isNil(visit) && hasValue(visit[propertyName]);
|
const visitHasProperty = (visit, propertyName) => !isNil(visit) && hasValue(visit[propertyName]);
|
||||||
|
@ -59,13 +59,17 @@ export const processStatsFromVisits = (normalizedVisits) =>
|
||||||
{ os: {}, browsers: {}, referrers: {}, countries: {}, cities: {}, citiesForMap: {} }
|
{ os: {}, browsers: {}, referrers: {}, countries: {}, cities: {}, citiesForMap: {} }
|
||||||
);
|
);
|
||||||
|
|
||||||
export const normalizeVisits = map(({ userAgent, date, referer, visitLocation }) => ({
|
export const normalizeVisits = map(({ userAgent, date, referer, visitLocation }) => {
|
||||||
date,
|
const { browser, os } = parseUserAgent(userAgent);
|
||||||
browser: browserFromUserAgent(userAgent),
|
|
||||||
os: osFromUserAgent(userAgent),
|
return {
|
||||||
referer: extractDomain(referer),
|
date,
|
||||||
country: (visitLocation && visitLocation.countryName) || 'Unknown',
|
browser,
|
||||||
city: (visitLocation && visitLocation.cityName) || 'Unknown',
|
os,
|
||||||
latitude: visitLocation && visitLocation.latitude,
|
referer: extractDomain(referer),
|
||||||
longitude: visitLocation && visitLocation.longitude,
|
country: (visitLocation && visitLocation.countryName) || 'Unknown',
|
||||||
}));
|
city: (visitLocation && visitLocation.cityName) || 'Unknown',
|
||||||
|
latitude: visitLocation && visitLocation.latitude,
|
||||||
|
longitude: visitLocation && visitLocation.longitude,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
|
@ -31,12 +31,16 @@ describe('<GraphCard />', () => {
|
||||||
expect(datasets).toHaveLength(1);
|
expect(datasets).toHaveLength(1);
|
||||||
expect(backgroundColor).toEqual([
|
expect(backgroundColor).toEqual([
|
||||||
'#97BBCD',
|
'#97BBCD',
|
||||||
'#DCDCDC',
|
|
||||||
'#F7464A',
|
'#F7464A',
|
||||||
'#46BFBD',
|
'#46BFBD',
|
||||||
'#FDB45C',
|
'#FDB45C',
|
||||||
'#949FB1',
|
'#949FB1',
|
||||||
'#4D5360',
|
'#57A773',
|
||||||
|
'#414066',
|
||||||
|
'#08B2E3',
|
||||||
|
'#B6C454',
|
||||||
|
'#DCDCDC',
|
||||||
|
'#463730',
|
||||||
]);
|
]);
|
||||||
expect(borderColor).toEqual('white');
|
expect(borderColor).toEqual('white');
|
||||||
expect(legend).toEqual({ position: 'right' });
|
expect(legend).toEqual({ position: 'right' });
|
||||||
|
|
|
@ -56,7 +56,7 @@ describe('VisitsParser', () => {
|
||||||
expect(os).toEqual({
|
expect(os).toEqual({
|
||||||
Linux: 3,
|
Linux: 3,
|
||||||
Windows: 1,
|
Windows: 1,
|
||||||
MacOS: 1,
|
macOS: 1,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ describe('VisitsParser', () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
browser: 'Firefox',
|
browser: 'Firefox',
|
||||||
os: 'MacOS',
|
os: 'macOS',
|
||||||
referer: 'google.com',
|
referer: 'google.com',
|
||||||
country: 'United States',
|
country: 'United States',
|
||||||
city: 'New York',
|
city: 'New York',
|
||||||
|
|
Loading…
Reference in a new issue