-
Notifications
You must be signed in to change notification settings - Fork 650
Recent download stats by minor version #1941
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
simonrw
wants to merge
25
commits into
rust-lang:master
from
simonrw:recent-download-stats-by-minor-version
Closed
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
75b9c08
Include component for drawing download graph
simonrw 2af9cdb
Ensures that graph title shows up
simonrw 83c4033
Implement reverse sorting by semver versions
simonrw 10a5c83
Show labels in the UI with ".x" suffix
simonrw e14e67e
Include new backend route for fetching recent versions
simonrw b106574
Fix clippy lint about filter.next -> find
simonrw 0895199
Clean up fetching crate versions
simonrw 59ab873
API response should be sorted
simonrw 5280903
Serialize semver::Version rather than String
simonrw b94b5e9
Update recent_downloads test
simonrw e5d24f8
The definition of "recent" is now set up in Config
simonrw 63e4139
Apply suggestions from code review
simonrw c200569
Implements recent downloads graph by using new endpoint
simonrw c972520
Add backend route in mirage config for tests
simonrw 265d517
Prettify javascript code
simonrw e2efc50
Handle errors when reading json data
simonrw 9573d6f
Test that the graph endpoints exist
simonrw cf2a016
Add commented out test for no recent downloads
simonrw 263e64d
Fix lint
simonrw 1468d08
Revert "Fix lint"
simonrw 8a0f024
Merge branch 'master' into recent-download-stats-by-minor-version
simonrw ec68efe
Prevent implicit this linter warnings
simonrw 19fc64a
Merge branch 'master' into recent-download-stats-by-minor-version
simonrw f7baa0c
Revert lint config change and pass model correctly in template
simonrw b04947a
Merge remote-tracking branch 'origin/master' into recent-download-sta…
simonrw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import major from 'semver/functions/major'; | ||
import minor from 'semver/functions/minor'; | ||
import lt from 'semver/functions/lt'; | ||
import fetch from 'fetch'; | ||
import Component from '@ember/component'; | ||
|
||
// Component to plot downloadeds split by semver minor version. | ||
export default Component.extend({ | ||
resizeHandler: undefined, | ||
|
||
didInsertElement() { | ||
this._super(...arguments); | ||
|
||
this.resizeHandler = () => this.rerender(); | ||
window.addEventListener('resize', this.resizeHandler, false); | ||
document.addEventListener('googleChartsLoaded', this.resizeHandler, false); | ||
}, | ||
|
||
willDestroyElement() { | ||
window.removeEventListener('resize', this.resizeHandler); | ||
document.removeEventListener('googleChartsLoaded', this.resizeHandler); | ||
}, | ||
|
||
didRender() { | ||
this._super(...arguments); | ||
|
||
let data = this.model.versions; | ||
|
||
// Early exit if the google plotting libraries do not exist | ||
let show = data && window.google && window.googleChartsLoaded; | ||
this.element.style.display = show ? '' : 'none'; | ||
if (!show) { | ||
return; | ||
} | ||
|
||
// Fetch the recent downloads from the API | ||
fetch(`/api/v1/crates/${this.model.name}/recent_downloads`) | ||
.then(async r => { | ||
if (!r.ok) { | ||
console.error('error fetching recent downloads from API'); | ||
return; | ||
} | ||
|
||
let recentDownloads; | ||
try { | ||
recentDownloads = await r.json(); | ||
} catch (e) { | ||
console.error(e); | ||
return; | ||
} | ||
|
||
// Build up the list of unique major.minor versions of this crate, and total | ||
// up the number of downloads for each semver version | ||
let downloadsPerVersion = new Map(); | ||
recentDownloads.downloads.forEach(v => { | ||
let mj = major(v.version); | ||
let mn = minor(v.version); | ||
let downloads = v.downloads; | ||
|
||
// XXX ugly hack to get semver to parse the version correctly later on. | ||
// We want to do a semver-aware sort, but the `semver.lt` function only | ||
// understands version triples, not doubles. | ||
let key = `${mj}.${mn}.0`; | ||
if (downloadsPerVersion.has(key)) { | ||
let old = downloadsPerVersion.get(key); | ||
downloadsPerVersion.set(key, old + downloads); | ||
} else { | ||
downloadsPerVersion.set(key, downloads); | ||
} | ||
}); | ||
|
||
// Build up the plotting data | ||
let plotData = [ | ||
// Headings and the nature of additional parameters | ||
['Version', 'Downloads', { role: 'style' }, { role: 'annotation' }], | ||
]; | ||
|
||
// Update plotData with rows in the correct format for google visualization library | ||
for (let [key, value] of sortIncreasingBySemver(downloadsPerVersion)) { | ||
plotData.push([key, value, '#62865f', value]); | ||
} | ||
|
||
let myData = window.google.visualization.arrayToDataTable(plotData); | ||
|
||
// Plot options | ||
let options = { | ||
chart: { | ||
title: 'Downloads', | ||
}, | ||
chartArea: { left: 85, width: '77%', height: '80%' }, | ||
hAxis: { | ||
minorGridlines: { count: 8 }, | ||
}, | ||
vAxis: { | ||
minorGridlines: { count: 5 }, | ||
viewWindow: { min: 0 }, | ||
}, | ||
legend: { position: 'none' }, | ||
}; | ||
|
||
// Draw the plot into the current element | ||
let chart = new window.google.visualization.BarChart(this.element); | ||
chart.draw(myData, options); | ||
}) | ||
.catch(e => console.error(`Error fetching data from API: ${e}`)); | ||
}, | ||
}); | ||
|
||
function sortIncreasingBySemver(downloadsMap) { | ||
const items = Array.from(downloadsMap.entries()); | ||
// Sort by semver comparison | ||
items.sort(([versionA], [versionB]) => { | ||
// Index 0 is the version string in the array. | ||
// | ||
// We use `lt` here as we want the array to be sorted in reverse order | ||
// (newest at the top) | ||
return lt(versionA, versionB); | ||
}); | ||
|
||
// Update the labels to show e.g. `0.1.x` instead of `0.1.0` which is | ||
// required by semver comparisons | ||
return items.map(([version, count]) => { | ||
return [version.replace(/\.0$/, '.x'), count]; | ||
}); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,26 +8,28 @@ use std::cmp; | |
use crate::controllers::frontend_prelude::*; | ||
|
||
use crate::models::{Crate, CrateVersions, Version, VersionDownload}; | ||
use crate::schema::version_downloads; | ||
use crate::schema::{version_downloads, versions}; | ||
use crate::views::EncodableVersionDownload; | ||
|
||
use crate::models::krate::to_char; | ||
|
||
use diesel::sql_types::BigInt; | ||
|
||
/// Handles the `GET /crates/:crate_id/downloads` route. | ||
pub fn downloads(req: &mut dyn Request) -> AppResult<Response> { | ||
use diesel::dsl::*; | ||
use diesel::sql_types::BigInt; | ||
|
||
let crate_name = &req.params()["crate_id"]; | ||
let conn = req.db_read_only()?; | ||
let krate = Crate::by_name(crate_name).first::<Crate>(&*conn)?; | ||
let ndays = req.app().config.ndays; | ||
|
||
let mut versions = krate.all_versions().load::<Version>(&*conn)?; | ||
versions.sort_by(|a, b| b.num.cmp(&a.num)); | ||
let (latest_five, rest) = versions.split_at(cmp::min(5, versions.len())); | ||
|
||
let downloads = VersionDownload::belonging_to(latest_five) | ||
.filter(version_downloads::date.gt(date(now - 90.days()))) | ||
.filter(version_downloads::date.gt(date(now - ndays.days()))) | ||
.order(version_downloads::date.asc()) | ||
.load(&*conn)? | ||
.into_iter() | ||
|
@@ -40,7 +42,7 @@ pub fn downloads(req: &mut dyn Request) -> AppResult<Response> { | |
to_char(version_downloads::date, "YYYY-MM-DD"), | ||
sum_downloads, | ||
)) | ||
.filter(version_downloads::date.gt(date(now - 90.days()))) | ||
.filter(version_downloads::date.gt(date(now - ndays.days()))) | ||
.group_by(version_downloads::date) | ||
.order(version_downloads::date.asc()) | ||
.load::<ExtraDownload>(&*conn)?; | ||
|
@@ -67,3 +69,53 @@ pub fn downloads(req: &mut dyn Request) -> AppResult<Response> { | |
meta, | ||
})) | ||
} | ||
|
||
/// Handles the `GET /crates/:crate_id/recent_downloads` route. | ||
pub fn recent_downloads(req: &mut dyn Request) -> AppResult<Response> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jtgeibel any thoughts on this? it might conflict with our plans to move the historical download data to S3 🤔 |
||
use diesel::dsl::*; | ||
|
||
let crate_name = &req.params()["crate_id"]; | ||
let conn = req.db_conn()?; | ||
let krate = Crate::by_name(crate_name).first::<Crate>(&*conn)?; | ||
let ndays = req.app().config.ndays; | ||
|
||
// Get the versions for this crate | ||
let available_versions = krate.all_versions().load::<Version>(&*conn)?; | ||
|
||
#[derive(Debug, Serialize, Queryable)] | ||
struct Download { | ||
version: semver::Version, | ||
downloads: i64, | ||
} | ||
|
||
#[derive(Debug, Serialize)] | ||
struct Response<'a> { | ||
downloads: Vec<Download>, | ||
meta: Meta<'a>, | ||
} | ||
|
||
#[derive(Debug, Serialize)] | ||
struct Meta<'a> { | ||
#[serde(rename = "crate")] | ||
krate: &'a str, | ||
ndays: i32, | ||
} | ||
|
||
// Now get the grouped versions for the last `ndays` days. | ||
let sum_downloads = sql::<BigInt>("SUM(version_downloads.downloads)"); | ||
let downloads = VersionDownload::belonging_to(available_versions.as_slice()) | ||
.inner_join(versions::table) | ||
.select((versions::num, sum_downloads)) | ||
.filter(version_downloads::date.gt(date(now - ndays.days()))) | ||
.group_by(versions::num) | ||
.order_by(versions::num.asc()) | ||
.load::<Download>(&*conn)?; | ||
|
||
Ok(req.json(&Response { | ||
downloads, | ||
meta: Meta { | ||
krate: crate_name, | ||
ndays, | ||
}, | ||
})) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.