2018-07-31 17:07:27 +00:00
|
|
|
require("./Stats.css");
|
2018-04-04 05:16:09 +00:00
|
|
|
|
2018-07-31 17:07:27 +00:00
|
|
|
const React = require("react");
|
|
|
|
const PropTypes = require("prop-types");
|
|
|
|
const formatBytes = require("pretty-bytes");
|
|
|
|
const formatDate = require("date-fns/format");
|
|
|
|
const parseDate = require("date-fns/parse");
|
|
|
|
const { continents, countries } = require("countries-list");
|
2018-04-04 05:16:09 +00:00
|
|
|
|
2018-07-31 17:07:27 +00:00
|
|
|
const formatNumber = require("../utils/formatNumber");
|
|
|
|
const formatPercent = require("../utils/formatPercent");
|
2017-08-22 15:31:33 +00:00
|
|
|
|
2018-04-04 05:16:09 +00:00
|
|
|
function getCountriesByContinent(continent) {
|
|
|
|
return Object.keys(countries).filter(
|
2018-02-18 02:00:56 +00:00
|
|
|
country => countries[country].continent === continent
|
|
|
|
);
|
2018-04-04 05:16:09 +00:00
|
|
|
}
|
2017-08-22 15:31:33 +00:00
|
|
|
|
2018-04-04 05:16:09 +00:00
|
|
|
function sumKeyValues(hash, keys) {
|
|
|
|
return keys.reduce((n, key) => n + (hash[key] || 0), 0);
|
|
|
|
}
|
2017-08-22 15:31:33 +00:00
|
|
|
|
2018-04-04 05:16:09 +00:00
|
|
|
function sumValues(hash) {
|
|
|
|
return Object.keys(hash).reduce((memo, key) => memo + hash[key], 0);
|
|
|
|
}
|
2016-05-20 18:58:58 +00:00
|
|
|
|
|
|
|
class Stats extends React.Component {
|
2016-07-20 19:26:15 +00:00
|
|
|
static propTypes = {
|
2017-08-22 15:31:33 +00:00
|
|
|
data: PropTypes.object
|
2018-02-18 02:00:56 +00:00
|
|
|
};
|
2016-05-20 18:58:58 +00:00
|
|
|
|
|
|
|
state = {
|
2017-08-22 16:12:57 +00:00
|
|
|
minPackageRequests: 1000000,
|
2017-08-22 15:31:33 +00:00
|
|
|
minCountryRequests: 1000000
|
2018-02-18 02:00:56 +00:00
|
|
|
};
|
2017-04-15 04:01:23 +00:00
|
|
|
|
2017-03-26 06:53:54 +00:00
|
|
|
render() {
|
2018-02-18 02:00:56 +00:00
|
|
|
const { data } = this.props;
|
2017-08-22 15:31:33 +00:00
|
|
|
|
2018-02-18 02:00:56 +00:00
|
|
|
if (data == null) return null;
|
2017-08-22 15:31:33 +00:00
|
|
|
|
2018-02-18 02:00:56 +00:00
|
|
|
const totals = data.totals;
|
2016-05-20 18:58:58 +00:00
|
|
|
|
|
|
|
// Summary data
|
2018-02-18 02:00:56 +00:00
|
|
|
const since = parseDate(totals.since);
|
|
|
|
const until = parseDate(totals.until);
|
2017-08-22 15:31:33 +00:00
|
|
|
|
|
|
|
// Packages
|
2018-02-18 02:00:56 +00:00
|
|
|
const packageRows = [];
|
2017-08-22 15:31:33 +00:00
|
|
|
|
2017-11-08 16:57:15 +00:00
|
|
|
Object.keys(totals.requests.package)
|
|
|
|
.sort((a, b) => {
|
2018-02-18 02:00:56 +00:00
|
|
|
return totals.requests.package[b] - totals.requests.package[a];
|
2017-11-08 16:57:15 +00:00
|
|
|
})
|
|
|
|
.forEach(packageName => {
|
2018-02-18 02:00:56 +00:00
|
|
|
const requests = totals.requests.package[packageName];
|
|
|
|
const bandwidth = totals.bandwidth.package[packageName];
|
2017-11-08 16:57:15 +00:00
|
|
|
|
|
|
|
if (requests >= this.state.minPackageRequests) {
|
|
|
|
packageRows.push(
|
|
|
|
<tr key={packageName}>
|
|
|
|
<td>
|
|
|
|
<a
|
|
|
|
href={`https://npmjs.org/package/${packageName}`}
|
|
|
|
title={`${packageName} on npm`}
|
|
|
|
>
|
|
|
|
{packageName}
|
|
|
|
</a>
|
|
|
|
</td>
|
|
|
|
<td>
|
2018-02-18 02:00:56 +00:00
|
|
|
{formatNumber(requests)} ({formatPercent(
|
|
|
|
requests / totals.requests.all
|
|
|
|
)}%)
|
2017-11-08 16:57:15 +00:00
|
|
|
</td>
|
|
|
|
{bandwidth ? (
|
|
|
|
<td>
|
2018-02-18 02:00:56 +00:00
|
|
|
{formatBytes(bandwidth)} ({formatPercent(
|
|
|
|
bandwidth / totals.bandwidth.all
|
|
|
|
)}%)
|
2017-11-08 16:57:15 +00:00
|
|
|
</td>
|
|
|
|
) : (
|
|
|
|
<td>-</td>
|
|
|
|
)}
|
|
|
|
</tr>
|
2018-02-18 02:00:56 +00:00
|
|
|
);
|
2017-11-08 16:57:15 +00:00
|
|
|
}
|
2018-02-18 02:00:56 +00:00
|
|
|
});
|
2016-05-20 18:58:58 +00:00
|
|
|
|
2017-08-22 15:31:33 +00:00
|
|
|
// Regions
|
2018-02-18 02:00:56 +00:00
|
|
|
const regionRows = [];
|
2016-05-20 18:58:58 +00:00
|
|
|
|
2017-08-22 15:31:33 +00:00
|
|
|
const continentsData = Object.keys(continents).reduce((memo, continent) => {
|
2018-02-18 02:00:56 +00:00
|
|
|
const localCountries = getCountriesByContinent(continent);
|
2016-05-20 18:58:58 +00:00
|
|
|
|
|
|
|
memo[continent] = {
|
2017-08-22 15:31:33 +00:00
|
|
|
countries: localCountries,
|
|
|
|
requests: sumKeyValues(totals.requests.country, localCountries),
|
|
|
|
bandwidth: sumKeyValues(totals.bandwidth.country, localCountries)
|
2018-02-18 02:00:56 +00:00
|
|
|
};
|
2016-05-20 18:58:58 +00:00
|
|
|
|
2018-02-18 02:00:56 +00:00
|
|
|
return memo;
|
|
|
|
}, {});
|
2016-05-20 18:58:58 +00:00
|
|
|
|
2017-08-22 15:31:33 +00:00
|
|
|
const topContinents = Object.keys(continentsData).sort((a, b) => {
|
2018-02-18 02:00:56 +00:00
|
|
|
return continentsData[b].requests - continentsData[a].requests;
|
|
|
|
});
|
2016-05-20 18:58:58 +00:00
|
|
|
|
|
|
|
topContinents.forEach(continent => {
|
2018-02-18 02:00:56 +00:00
|
|
|
const continentName = continents[continent];
|
|
|
|
const continentData = continentsData[continent];
|
2016-05-20 18:58:58 +00:00
|
|
|
|
2018-02-18 02:00:56 +00:00
|
|
|
if (
|
|
|
|
continentData.requests > this.state.minCountryRequests &&
|
|
|
|
continentData.bandwidth !== 0
|
|
|
|
) {
|
2016-05-20 18:58:58 +00:00
|
|
|
regionRows.push(
|
|
|
|
<tr key={continent} className="continent-row">
|
2017-11-08 16:57:15 +00:00
|
|
|
<td>
|
2018-04-04 05:16:09 +00:00
|
|
|
<strong>{continentName}</strong>
|
2017-11-08 16:57:15 +00:00
|
|
|
</td>
|
|
|
|
<td>
|
2018-04-04 05:16:09 +00:00
|
|
|
<strong>
|
|
|
|
{formatNumber(continentData.requests)} ({formatPercent(
|
|
|
|
continentData.requests / totals.requests.all
|
|
|
|
)}%)
|
|
|
|
</strong>
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
<strong>
|
|
|
|
{formatBytes(continentData.bandwidth)} ({formatPercent(
|
|
|
|
continentData.bandwidth / totals.bandwidth.all
|
|
|
|
)}%)
|
|
|
|
</strong>
|
2017-11-08 16:57:15 +00:00
|
|
|
</td>
|
2016-05-20 18:58:58 +00:00
|
|
|
</tr>
|
2018-02-18 02:00:56 +00:00
|
|
|
);
|
2016-05-20 18:58:58 +00:00
|
|
|
|
2017-08-22 15:31:33 +00:00
|
|
|
const topCountries = continentData.countries.sort((a, b) => {
|
2018-02-18 02:00:56 +00:00
|
|
|
return totals.requests.country[b] - totals.requests.country[a];
|
|
|
|
});
|
2016-05-20 18:58:58 +00:00
|
|
|
|
|
|
|
topCountries.forEach(country => {
|
2018-02-18 02:00:56 +00:00
|
|
|
const countryRequests = totals.requests.country[country];
|
|
|
|
const countryBandwidth = totals.bandwidth.country[country];
|
2016-05-20 18:58:58 +00:00
|
|
|
|
2017-08-22 15:31:33 +00:00
|
|
|
if (countryRequests > this.state.minCountryRequests) {
|
2016-05-20 18:58:58 +00:00
|
|
|
regionRows.push(
|
|
|
|
<tr key={continent + country} className="country-row">
|
2017-08-22 15:31:33 +00:00
|
|
|
<td className="country-name">{countries[country].name}</td>
|
2017-11-08 16:57:15 +00:00
|
|
|
<td>
|
|
|
|
{formatNumber(countryRequests)} ({formatPercent(
|
|
|
|
countryRequests / totals.requests.all
|
|
|
|
)}%)
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{formatBytes(countryBandwidth)} ({formatPercent(
|
|
|
|
countryBandwidth / totals.bandwidth.all
|
|
|
|
)}%)
|
|
|
|
</td>
|
2016-05-20 18:58:58 +00:00
|
|
|
</tr>
|
2018-02-18 02:00:56 +00:00
|
|
|
);
|
2016-05-20 18:58:58 +00:00
|
|
|
}
|
2018-02-18 02:00:56 +00:00
|
|
|
});
|
2016-05-20 18:58:58 +00:00
|
|
|
}
|
2018-02-18 02:00:56 +00:00
|
|
|
});
|
2016-05-20 18:58:58 +00:00
|
|
|
|
2017-08-22 17:14:21 +00:00
|
|
|
// Protocols
|
2017-11-08 16:57:15 +00:00
|
|
|
const protocolRows = Object.keys(totals.requests.protocol)
|
|
|
|
.sort((a, b) => {
|
2018-02-18 02:00:56 +00:00
|
|
|
return totals.requests.protocol[b] - totals.requests.protocol[a];
|
2017-11-08 16:57:15 +00:00
|
|
|
})
|
|
|
|
.map(protocol => {
|
2018-02-18 02:00:56 +00:00
|
|
|
const requests = totals.requests.protocol[protocol];
|
2017-11-08 16:57:15 +00:00
|
|
|
|
|
|
|
return (
|
|
|
|
<tr key={protocol}>
|
|
|
|
<td>{protocol}</td>
|
|
|
|
<td>
|
|
|
|
{formatNumber(requests)} ({formatPercent(
|
|
|
|
requests / sumValues(totals.requests.protocol)
|
|
|
|
)}%)
|
|
|
|
</td>
|
|
|
|
</tr>
|
2018-02-18 02:00:56 +00:00
|
|
|
);
|
|
|
|
});
|
2017-08-22 17:14:21 +00:00
|
|
|
|
2016-05-20 18:58:58 +00:00
|
|
|
return (
|
|
|
|
<div className="wrapper">
|
2017-11-08 16:57:15 +00:00
|
|
|
<p>
|
2017-11-25 21:25:01 +00:00
|
|
|
From <strong>{formatDate(since, "MMM D")}</strong> to{" "}
|
|
|
|
<strong>{formatDate(until, "MMM D")}</strong> unpkg served{" "}
|
2018-02-18 02:00:56 +00:00
|
|
|
<strong>{formatNumber(totals.requests.all)}</strong> requests and a
|
|
|
|
total of <strong>{formatBytes(totals.bandwidth.all)}</strong> of data
|
|
|
|
to <strong>{formatNumber(totals.uniques.all)}</strong> unique
|
|
|
|
visitors,{" "}
|
|
|
|
<strong>
|
|
|
|
{formatPercent(totals.requests.cached / totals.requests.all, 0)}%
|
|
|
|
</strong>{" "}
|
|
|
|
of which were served from the cache.
|
2017-11-08 16:57:15 +00:00
|
|
|
</p>
|
2017-08-22 15:31:33 +00:00
|
|
|
|
|
|
|
<h3>Packages</h3>
|
|
|
|
|
2017-11-08 16:57:15 +00:00
|
|
|
<p>
|
2017-11-25 21:25:01 +00:00
|
|
|
The table below shows the most popular packages served by unpkg from{" "}
|
|
|
|
<strong>{formatDate(since, "MMM D")}</strong> to{" "}
|
|
|
|
<strong>{formatDate(until, "MMM D")}</strong>. Only the top{" "}
|
2017-11-08 16:57:15 +00:00
|
|
|
{Object.keys(totals.requests.package).length} packages are shown.
|
|
|
|
</p>
|
2017-08-22 16:12:57 +00:00
|
|
|
|
2017-11-08 16:57:15 +00:00
|
|
|
<p className="table-filter">
|
2017-11-25 21:25:01 +00:00
|
|
|
Include only packages that received at least{" "}
|
2017-11-08 16:57:15 +00:00
|
|
|
<select
|
2017-08-22 15:31:33 +00:00
|
|
|
value={this.state.minPackageRequests}
|
2017-11-08 16:57:15 +00:00
|
|
|
onChange={event =>
|
|
|
|
this.setState({
|
|
|
|
minPackageRequests: parseInt(event.target.value, 10)
|
|
|
|
})
|
|
|
|
}
|
2017-08-22 15:31:33 +00:00
|
|
|
>
|
|
|
|
<option value="0">0</option>
|
|
|
|
<option value="1000">1,000</option>
|
|
|
|
<option value="10000">10,000</option>
|
|
|
|
<option value="100000">100,000</option>
|
|
|
|
<option value="1000000">1,000,000</option>
|
2017-08-22 16:30:57 +00:00
|
|
|
<option value="10000000">10,000,000</option>
|
2017-11-25 21:25:01 +00:00
|
|
|
</select>{" "}
|
2017-11-08 16:57:15 +00:00
|
|
|
requests.
|
2017-08-22 15:31:33 +00:00
|
|
|
</p>
|
2016-05-20 18:58:58 +00:00
|
|
|
|
2017-11-25 21:25:01 +00:00
|
|
|
<table cellSpacing="0" cellPadding="0" style={{ width: "100%" }}>
|
2017-08-22 15:31:33 +00:00
|
|
|
<thead>
|
|
|
|
<tr>
|
2018-04-04 05:16:09 +00:00
|
|
|
<th>
|
|
|
|
<strong>Package</strong>
|
|
|
|
</th>
|
|
|
|
<th>
|
|
|
|
<strong>Requests (% of total)</strong>
|
|
|
|
</th>
|
|
|
|
<th>
|
|
|
|
<strong>Bandwidth (% of total)</strong>
|
|
|
|
</th>
|
2017-08-22 15:31:33 +00:00
|
|
|
</tr>
|
|
|
|
</thead>
|
2017-11-08 16:57:15 +00:00
|
|
|
<tbody>{packageRows}</tbody>
|
2017-08-22 15:31:33 +00:00
|
|
|
</table>
|
2016-05-20 18:58:58 +00:00
|
|
|
|
2017-08-22 15:31:33 +00:00
|
|
|
<h3>Regions</h3>
|
|
|
|
|
2017-11-08 16:57:15 +00:00
|
|
|
<p>
|
2017-11-25 21:25:01 +00:00
|
|
|
The table below breaks down requests to unpkg from{" "}
|
|
|
|
<strong>{formatDate(since, "MMM D")}</strong> to{" "}
|
|
|
|
<strong>{formatDate(until, "MMM D")}</strong> by geographic region.
|
2017-11-08 16:57:15 +00:00
|
|
|
</p>
|
2017-08-22 16:12:57 +00:00
|
|
|
|
2017-11-08 16:57:15 +00:00
|
|
|
<p className="table-filter">
|
2017-11-25 21:25:01 +00:00
|
|
|
Include only countries that made at least{" "}
|
2017-11-08 16:57:15 +00:00
|
|
|
<select
|
2017-08-22 15:31:33 +00:00
|
|
|
value={this.state.minCountryRequests}
|
2017-11-08 16:57:15 +00:00
|
|
|
onChange={event =>
|
|
|
|
this.setState({
|
|
|
|
minCountryRequests: parseInt(event.target.value, 10)
|
|
|
|
})
|
|
|
|
}
|
2017-08-22 15:31:33 +00:00
|
|
|
>
|
|
|
|
<option value="0">0</option>
|
|
|
|
<option value="100000">100,000</option>
|
|
|
|
<option value="1000000">1,000,000</option>
|
|
|
|
<option value="10000000">10,000,000</option>
|
|
|
|
<option value="100000000">100,000,000</option>
|
2017-11-25 21:25:01 +00:00
|
|
|
</select>{" "}
|
2017-11-08 16:57:15 +00:00
|
|
|
requests.
|
2017-08-22 15:31:33 +00:00
|
|
|
</p>
|
|
|
|
|
2018-02-18 02:00:56 +00:00
|
|
|
<table
|
|
|
|
cellSpacing="0"
|
|
|
|
cellPadding="0"
|
|
|
|
style={{ width: "100%" }}
|
|
|
|
className="regions-table"
|
|
|
|
>
|
2017-08-22 15:31:33 +00:00
|
|
|
<thead>
|
|
|
|
<tr>
|
2018-04-04 05:16:09 +00:00
|
|
|
<th>
|
|
|
|
<strong>Region</strong>
|
|
|
|
</th>
|
|
|
|
<th>
|
|
|
|
<strong>Requests (% of total)</strong>
|
|
|
|
</th>
|
|
|
|
<th>
|
|
|
|
<strong>Bandwidth (% of total)</strong>
|
|
|
|
</th>
|
2016-05-20 18:58:58 +00:00
|
|
|
</tr>
|
|
|
|
</thead>
|
2017-11-08 16:57:15 +00:00
|
|
|
<tbody>{regionRows}</tbody>
|
2016-05-20 18:58:58 +00:00
|
|
|
</table>
|
2017-08-22 17:14:21 +00:00
|
|
|
|
|
|
|
<h3>Protocols</h3>
|
|
|
|
|
2017-11-08 16:57:15 +00:00
|
|
|
<p>
|
2017-11-25 21:25:01 +00:00
|
|
|
The table below breaks down requests to unpkg from{" "}
|
|
|
|
<strong>{formatDate(since, "MMM D")}</strong> to{" "}
|
|
|
|
<strong>{formatDate(until, "MMM D")}</strong> by HTTP protocol.
|
2017-11-08 16:57:15 +00:00
|
|
|
</p>
|
2017-08-22 17:14:21 +00:00
|
|
|
|
2017-11-25 21:25:01 +00:00
|
|
|
<table cellSpacing="0" cellPadding="0" style={{ width: "100%" }}>
|
2017-08-22 17:14:21 +00:00
|
|
|
<thead>
|
|
|
|
<tr>
|
2018-04-04 05:16:09 +00:00
|
|
|
<th>
|
|
|
|
<strong>Protocol</strong>
|
|
|
|
</th>
|
|
|
|
<th>
|
|
|
|
<strong>Requests (% of total)</strong>
|
|
|
|
</th>
|
2017-08-22 17:14:21 +00:00
|
|
|
</tr>
|
|
|
|
</thead>
|
2017-11-08 16:57:15 +00:00
|
|
|
<tbody>{protocolRows}</tbody>
|
2017-08-22 17:14:21 +00:00
|
|
|
</table>
|
2016-05-20 18:58:58 +00:00
|
|
|
</div>
|
2018-02-18 02:00:56 +00:00
|
|
|
);
|
2016-05-20 18:58:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-31 17:07:27 +00:00
|
|
|
module.exports = Stats;
|