From bc182a578043847a5697e891750e6d2f2824bdb2 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Mon, 10 Jul 2017 14:03:56 -0400 Subject: [PATCH 01/37] query for recent_downloads --- src/krate.rs | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/krate.rs b/src/krate.rs index 1a6435ce9ce..057ba7aaddf 100644 --- a/src/krate.rs +++ b/src/krate.rs @@ -19,6 +19,7 @@ use serde_json; use semver; use time::Timespec; use url::Url; +use chrono::NaiveDate; use app::{App, RequestApp}; use badge::EncodableBadge; @@ -39,7 +40,18 @@ use util::{RequestUtils, CargoResult, internal, ChainError, human}; use version::{EncodableVersion, NewVersion}; use {Model, User, Keyword, Version, Category, Badge, Replica}; -#[derive(Debug, Clone, Queryable, Identifiable, AsChangeset)] +#[derive(Queryable, Identifiable, Associations, AsChangeset)] +#[belongs_to(Crate)] +#[primary_key(crate_id, date)] +#[table_name="crate_downloads"] +pub struct CrateDownload { + pub crate_id: i32, + pub downloads: i32, + pub date: NaiveDate, +} + +#[derive(Debug, Clone, Queryable, Identifiable, Associations, AsChangeset)] +#[has_many(crate_downloads)] pub struct Crate { pub id: i32, pub name: String, @@ -728,16 +740,23 @@ impl Model for Crate { /// Handles the `GET /crates` route. pub fn index(req: &mut Request) -> CargoResult { - use diesel::expression::AsExpression; - use diesel::types::Bool; + use diesel::expression::{AsExpression, DayAndMonthIntervalDsl}; + use diesel::types::{Bool, BigInt}; + use diesel::expression::functions::date_and_time::{now, date}; + use diesel::expression::sql_literal::sql; let conn = req.db_conn()?; let (offset, limit) = req.pagination(10, 100)?; let params = req.query(); let sort = params.get("sort").map(|s| &**s).unwrap_or("alpha"); + let recent_downloads = sql::("SUM(crate_downloads.downloads)"); + let mut query = crates::table - .select((ALL_COLUMNS, AsExpression::::as_expression(false))) + .inner_join(crate_downloads::table) + .group_by(crates::id) + .filter(crate_downloads::date.gt(date(now - 90.days()))) + .select((ALL_COLUMNS, AsExpression::::as_expression(false), recent_downloads.clone())) .into_boxed(); if sort == "downloads" { @@ -754,7 +773,7 @@ pub fn index(req: &mut Request) -> CargoResult { ), )); - query = query.select((ALL_COLUMNS, crates::name.eq(q_string))); + query = query.select((ALL_COLUMNS, crates::name.eq(q_string), recent_downloads.clone())); let perfect_match = crates::name.eq(q_string).desc(); if sort == "downloads" { query = query.order((perfect_match, crates::downloads.desc())); @@ -824,14 +843,14 @@ pub fn index(req: &mut Request) -> CargoResult { )); } - let data = query.paginate(limit, offset).load::<((Crate, bool), i64)>( + let data = query.paginate(limit, offset).load::<((Crate, bool, i64), i64)>( &*conn, )?; let total = data.first().map(|&(_, t)| t).unwrap_or(0); let crates = data.iter() - .map(|&((ref c, _), _)| c.clone()) + .map(|&((ref c, _, _), _)| c.clone()) .collect::>(); - let perfect_matches = data.into_iter().map(|((_, b), _)| b).collect::>(); + let perfect_matches = data.into_iter().map(|((_, b, _), _)| b).collect::>(); let versions = Version::belonging_to(&crates) .load::(&*conn)? From 36fe81cffbf47f0176c1d5a7229aa3ce46d9fb7e Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Mon, 10 Jul 2017 15:18:30 -0400 Subject: [PATCH 02/37] correctly displays top recently downloaded crates --- src/krate.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/krate.rs b/src/krate.rs index 057ba7aaddf..79619c79b58 100644 --- a/src/krate.rs +++ b/src/krate.rs @@ -51,7 +51,6 @@ pub struct CrateDownload { } #[derive(Debug, Clone, Queryable, Identifiable, Associations, AsChangeset)] -#[has_many(crate_downloads)] pub struct Crate { pub id: i32, pub name: String, @@ -760,7 +759,7 @@ pub fn index(req: &mut Request) -> CargoResult { .into_boxed(); if sort == "downloads" { - query = query.order(crates::downloads.desc()) + query = query.order(recent_downloads.clone().desc()) } else { query = query.order(crates::name.asc()) } From 5017507eb35153f218a00d089652b00ed3933053 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Mon, 10 Jul 2017 16:17:08 -0400 Subject: [PATCH 03/37] Added recent_downloads to encodable_crate --- src/krate.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/krate.rs b/src/krate.rs index 79619c79b58..606410986df 100644 --- a/src/krate.rs +++ b/src/krate.rs @@ -111,6 +111,7 @@ pub struct EncodableCrate { pub badges: Option>, pub created_at: String, pub downloads: i32, + pub recent_downloads: i64, pub max_version: String, pub description: Option, pub homepage: Option, @@ -495,8 +496,9 @@ impl Crate { max_version: semver::Version, badges: Option>, exact_match: bool, + recent_downloads: i64, ) -> EncodableCrate { - self.encodable(max_version, None, None, None, badges, exact_match) + self.encodable(max_version, None, None, None, badges, exact_match, recent_downloads) } pub fn encodable( @@ -507,6 +509,7 @@ impl Crate { categories: Option<&[Category]>, badges: Option>, exact_match: bool, + recent_downloads: i64, ) -> EncodableCrate { let Crate { name, @@ -532,6 +535,7 @@ impl Crate { updated_at: ::encode_time(updated_at), created_at: ::encode_time(created_at), downloads: downloads, + recent_downloads: recent_downloads, versions: versions, keywords: keyword_ids, categories: category_ids, @@ -759,6 +763,8 @@ pub fn index(req: &mut Request) -> CargoResult { .into_boxed(); if sort == "downloads" { + query = query.order(crates::downloads.desc()) + } else if sort == "recent-downloads" { query = query.order(recent_downloads.clone().desc()) } else { query = query.order(crates::name.asc()) @@ -776,6 +782,8 @@ pub fn index(req: &mut Request) -> CargoResult { let perfect_match = crates::name.eq(q_string).desc(); if sort == "downloads" { query = query.order((perfect_match, crates::downloads.desc())); + } else if sort == "recent-downloads" { + query = query.order((perfect_match, recent_downloads.clone().desc())); } else { let rank = ts_rank_cd(crates::textsearchable_index_col, q); query = query.order((perfect_match, rank.desc())) @@ -849,7 +857,8 @@ pub fn index(req: &mut Request) -> CargoResult { let crates = data.iter() .map(|&((ref c, _, _), _)| c.clone()) .collect::>(); - let perfect_matches = data.into_iter().map(|((_, b, _), _)| b).collect::>(); + let perfect_matches = data.clone().into_iter().map(|((_, b, _), _)| b).collect::>(); + let recent_downloads = data.clone().into_iter().map(|((_, _, s), _)| s).collect::>(); let versions = Version::belonging_to(&crates) .load::(&*conn)? @@ -860,7 +869,8 @@ pub fn index(req: &mut Request) -> CargoResult { let crates = versions .zip(crates) .zip(perfect_matches) - .map(|((max_version, krate), perfect_match)| { + .zip(recent_downloads) + .map(|(((max_version, krate), perfect_match), recent_downloads)| { // FIXME: If we add crate_id to the Badge enum we can eliminate // this N+1 let badges = badges::table @@ -870,6 +880,7 @@ pub fn index(req: &mut Request) -> CargoResult { max_version, Some(badges), perfect_match, + recent_downloads, )) }) .collect::>()?; @@ -909,7 +920,7 @@ pub fn summary(req: &mut Request) -> CargoResult { .map(|versions| Version::max(versions.into_iter().map(|v| v.num))) .zip(krates) .map(|(max_version, krate)| { - Ok(krate.minimal_encodable(max_version, None, false)) + Ok(krate.minimal_encodable(max_version, None, false, 0)) }) .collect() }; @@ -1006,6 +1017,7 @@ pub fn show(req: &mut Request) -> CargoResult { Some(&cats), Some(badges), false, + 0, ), versions: versions .into_iter() @@ -1148,7 +1160,7 @@ pub fn new(req: &mut Request) -> CargoResult { warnings: Warnings<'a>, } Ok(req.json(&R { - krate: krate.minimal_encodable(max_version, None, false), + krate: krate.minimal_encodable(max_version, None, false, 0), warnings: warnings, })) }) From 776ed77d394bc3f96465730cad596bbbbeb635d1 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Mon, 10 Jul 2017 16:44:38 -0400 Subject: [PATCH 04/37] addition of dropdown selection for ability to sort by recent downloads --- app/controllers/crates.js | 10 +++++++++- app/controllers/search.js | 9 ++++++++- app/models/crate.js | 1 + app/templates/crates.hbs | 7 ++++++- app/templates/search.hbs | 7 ++++++- 5 files changed, 30 insertions(+), 4 deletions(-) diff --git a/app/controllers/crates.js b/app/controllers/crates.js index 185d93ca697..154fa17573a 100644 --- a/app/controllers/crates.js +++ b/app/controllers/crates.js @@ -14,6 +14,14 @@ export default Controller.extend(PaginationMixin, { totalItems: computed.readOnly('model.meta.total'), currentSortBy: computed('sort', function() { - return (this.get('sort') === 'downloads') ? 'Downloads' : 'Alphabetical'; + if (this.get('sort') === 'downloads') { + return 'All-Time Downloads'; + } else if (this.get('sort') === 'recent-downloads') { + return 'Recent Downloads'; + } else { + return 'Alphabetical'; + } + + //return (this.get('sort') === 'downloads') ? 'Downloads' : 'Alphabetical'; }), }); diff --git a/app/controllers/search.js b/app/controllers/search.js index a590e1348a9..74a4e8fa37a 100644 --- a/app/controllers/search.js +++ b/app/controllers/search.js @@ -13,7 +13,14 @@ export default Controller.extend(PaginationMixin, { totalItems: computed.readOnly('model.meta.total'), currentSortBy: computed('sort', function() { - return (this.get('sort') === 'downloads') ? 'Downloads' : 'Relevance'; + if (this.get('sort' === 'downloads')) { + return 'All-Time Downloads'; + } else if (this.get('sort') === 'recent-downloads') { + return 'Recent Downloads'; + } else { + return 'Relevance'; + } + //return (this.get('sort') === 'downloads') ? 'Downloads' : 'Relevance'; }), hasItems: computed.bool('totalItems'), diff --git a/app/models/crate.js b/app/models/crate.js index 67ed18cf4c3..44cf9bb710e 100644 --- a/app/models/crate.js +++ b/app/models/crate.js @@ -4,6 +4,7 @@ import DS from 'ember-data'; export default DS.Model.extend({ name: DS.attr('string'), downloads: DS.attr('number'), + recent_downloads: DS.attr('number'), created_at: DS.attr('date'), updated_at: DS.attr('date'), max_version: DS.attr('string'), diff --git a/app/templates/crates.hbs b/app/templates/crates.hbs index cc8a760b228..692ebd8a6a4 100644 --- a/app/templates/crates.hbs +++ b/app/templates/crates.hbs @@ -52,7 +52,12 @@
  • {{#link-to (query-params page=1 sort="downloads")}} - Downloads + All-Time Downloads + {{/link-to}} +
  • +
  • + {{#link-to (query-params page=1 sort="recent-downloads")}} + Recent Downloads {{/link-to}}
  • {{/rl-dropdown}} diff --git a/app/templates/search.hbs b/app/templates/search.hbs index e256327fb60..f221c60dce1 100644 --- a/app/templates/search.hbs +++ b/app/templates/search.hbs @@ -38,7 +38,12 @@
  • {{#link-to (query-params page=1 sort="downloads")}} - Downloads + All-Time Downloads + {{/link-to}} +
  • +
  • + {{#link-to (query-params page=1 sort="recent-downloads")}} + Recent Downloads {{/link-to}}
  • {{/rl-dropdown}} From 642a095f87b0b0ae961432b5d9e9f85d280d31ab Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 11 Jul 2017 12:32:20 -0400 Subject: [PATCH 05/37] all-time and in past 90 days downloads displayed in list, currently both totals are listed but has ability to show just one according to sort --- app/components/crate-row.js | 28 +++++++++++++++++++++++++- app/styles/crate.scss | 11 +++++++++- app/templates/components/crate-row.hbs | 6 +++++- app/templates/crates.hbs | 2 +- 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/app/components/crate-row.js b/app/components/crate-row.js index 8e2527acb16..ad3de53bdcd 100644 --- a/app/components/crate-row.js +++ b/app/components/crate-row.js @@ -1,5 +1,31 @@ import Component from '@ember/component'; +import { computed } from '@ember/object'; export default Component.extend({ - classNames: ['crate', 'row'] + classNames: ['crate', 'row'], + sort: null, + + isDownloads: computed('sort', function() { + if (this.get('sort') === 'All-Time Downloads') { + return true; + } else { + return false; + } + }), + + isRecentDownloads: computed('sort', function() { + if (this.get('sort') === 'Recent Downloads') { + return true; + } else { + return false; + } + }), + + isAlpha: computed('sort', function() { + if (this.get('sort') === 'Alphabetical') { + return true; + } else { + return false; + } + }) }); diff --git a/app/styles/crate.scss b/app/styles/crate.scss index ddf3f99ada5..5f3b9744c34 100644 --- a/app/styles/crate.scss +++ b/app/styles/crate.scss @@ -136,7 +136,16 @@ width: 15%; color: $main-color-light; } - .downloads { @include display-flex; @include align-items(center); } + .downloads { + @include display-flex; + @include align-items(center); + padding-bottom: 5px; + } + .recent-downloads { + @include display-flex; + @include align-items(center); + + } .rev-dep-downloads {padding-left: 7px} } diff --git a/app/templates/components/crate-row.hbs b/app/templates/components/crate-row.hbs index daf61b07799..f9318133dbe 100644 --- a/app/templates/components/crate-row.hbs +++ b/app/templates/components/crate-row.hbs @@ -20,7 +20,11 @@
    {{svg-jar "download"}} - {{ format-num crate.downloads }} + All-Time: {{ format-num crate.downloads }} +
    +
    + {{svg-jar "download"}} + Recent: {{ format-num crate.recent_downloads }}