From 166f495fa096bbdbc9370c75b40b49bf0073ea0b Mon Sep 17 00:00:00 2001 From: Mario Areias Date: Tue, 11 Oct 2022 22:14:28 +1100 Subject: [PATCH] Crate with all versions yanked display reduced info --- src/controllers/krate/metadata.rs | 17 +++++++++-- src/controllers/krate/publish.rs | 9 +++++- src/controllers/krate/search.rs | 13 +++++++- src/models/krate.rs | 10 ++++++ src/tests/krate/search.rs | 51 +++++++++++++++++++++++++++++++ src/views.rs | 21 +++++++++++-- 6 files changed, 115 insertions(+), 6 deletions(-) diff --git a/src/controllers/krate/metadata.rs b/src/controllers/krate/metadata.rs index 943f803019b..0f07861a37b 100644 --- a/src/controllers/krate/metadata.rs +++ b/src/controllers/krate/metadata.rs @@ -40,6 +40,7 @@ pub fn summary(req: &mut dyn RequestExt) -> EndpointResult { let krates = data.into_iter().map(|(c, _)| c).collect::>(); let versions: Vec = krates.versions().load(&*conn)?; + let all_versions_yanked = versions.is_empty(); versions .grouped_by(&krates) .into_iter() @@ -53,6 +54,7 @@ pub fn summary(req: &mut dyn RequestExt) -> EndpointResult { None, false, recent_downloads, + all_versions_yanked, )) }) .collect() @@ -125,6 +127,8 @@ pub fn summary(req: &mut dyn RequestExt) -> EndpointResult { /// Handles the `GET /crates/:crate_id` route. pub fn show(req: &mut dyn RequestExt) -> EndpointResult { + use diesel::dsl::exists; + let name = &req.params()["crate_id"]; let include = req .query() @@ -136,6 +140,14 @@ pub fn show(req: &mut dyn RequestExt) -> EndpointResult { let conn = req.db_read()?; let krate: Crate = Crate::by_name(name).first(&*conn)?; + let all_versions_yanked = if include.versions { + // this query checks if there any non-yanked versions for a crate. + // then inverts the result. + !diesel::select(exists(krate.versions())).get_result::(&*conn)? + } else { + false + }; + let versions_publishers_and_audit_actions = if include.versions { let mut versions_and_publishers: Vec<(Version, Option)> = krate .all_versions() @@ -193,7 +205,7 @@ pub fn show(req: &mut dyn RequestExt) -> EndpointResult { None }; - let badges = if include.badges { + let badges = if include.badges && !all_versions_yanked { Some( badges::table .filter(badges::crate_id.eq(krate.id)) @@ -202,7 +214,7 @@ pub fn show(req: &mut dyn RequestExt) -> EndpointResult { } else { None }; - let top_versions = if include.versions { + let top_versions = if include.versions && !all_versions_yanked { Some(krate.top_versions(&conn)?) } else { None @@ -217,6 +229,7 @@ pub fn show(req: &mut dyn RequestExt) -> EndpointResult { badges, false, recent_downloads, + all_versions_yanked, ); let encodable_versions = versions_publishers_and_audit_actions.map(|vpa| { vpa.into_iter() diff --git a/src/controllers/krate/publish.rs b/src/controllers/krate/publish.rs index 81b444c968d..b6658410d68 100644 --- a/src/controllers/krate/publish.rs +++ b/src/controllers/krate/publish.rs @@ -253,7 +253,14 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult { }; Ok(req.json(&GoodCrate { - krate: EncodableCrate::from_minimal(krate, Some(&top_versions), None, false, None), + krate: EncodableCrate::from_minimal( + krate, + Some(&top_versions), + None, + false, + None, + false, + ), warnings, })) }) diff --git a/src/controllers/krate/search.rs b/src/controllers/krate/search.rs index 75eb206fddc..8e0d9d6cbf0 100644 --- a/src/controllers/krate/search.rs +++ b/src/controllers/krate/search.rs @@ -313,6 +313,12 @@ pub fn search(req: &mut dyn RequestExt) -> EndpointResult { let crates = data.into_iter().map(|(c, _, _)| c).collect::>(); let versions: Vec = crates.versions().load(&*conn)?; + let yanked_crates = versions + .clone() + .grouped_by(&crates) + .into_iter() + .map(|v| v.is_empty()); + let versions = versions .grouped_by(&crates) .into_iter() @@ -331,14 +337,19 @@ pub fn search(req: &mut dyn RequestExt) -> EndpointResult { .zip(perfect_matches) .zip(recent_downloads) .zip(badges) + .zip(yanked_crates) .map( - |((((max_version, krate), perfect_match), recent_downloads), badges)| { + |( + ((((max_version, krate), perfect_match), recent_downloads), badges), + all_versions_yanked, + )| { EncodableCrate::from_minimal( krate, Some(&max_version), Some(badges), perfect_match, Some(recent_downloads), + all_versions_yanked, ) }, ) diff --git a/src/models/krate.rs b/src/models/krate.rs index bb707cf4543..e1044334e1b 100644 --- a/src/models/krate.rs +++ b/src/models/krate.rs @@ -199,6 +199,16 @@ impl<'a> NewCrate<'a> { } impl Crate { + pub fn from_yanked(krate: Crate) -> Crate { + Crate { + description: None, + homepage: None, + documentation: None, + repository: None, + ..krate + } + } + /// SQL filter based on whether the crate's name loosely matches the given /// string. /// diff --git a/src/tests/krate/search.rs b/src/tests/krate/search.rs index 3bbaf83c3cd..3315caacf9c 100644 --- a/src/tests/krate/search.rs +++ b/src/tests/krate/search.rs @@ -516,6 +516,57 @@ fn index_include_yanked() { assert_eq!(json.crates[2].name, "unyanked"); } +#[test] +fn yanked_crates() { + let (app, anon, user) = TestApp::init().with_user(); + let user = user.as_model(); + + app.db(|conn| { + //TODO Add badge test case here. + CrateBuilder::new("test_all_yanked", user.id) + .version(VersionBuilder::new("1.0.0").yanked(true)) + .version(VersionBuilder::new("2.0.0").yanked(true)) + .description("yanked crate") + .homepage("https://github.com/test/yanked-crate") + .documentation("https://yanked-crate.github.io") + .expect_build(conn); + + CrateBuilder::new("test_unyanked", user.id) + .version(VersionBuilder::new("1.0.0")) + .version(VersionBuilder::new("2.0.0")) + .description("unyanked crate") + .homepage("https://github.com/test/unyanked-crate") + .documentation("https://unyanked-crate.github.io") + .expect_build(conn); + }); + + let json = anon.search("q=test"); + assert_eq!(json.meta.total, 2); + + assert_eq!(json.crates[0].name, "test_all_yanked"); + assert_eq!(json.crates[0].max_version, "0.0.0"); + assert_eq!(json.crates[0].badges, None); + assert_eq!(json.crates[0].documentation, None); + assert_eq!(json.crates[0].homepage, None); + assert_eq!(json.crates[0].description, None); + + assert_eq!(json.crates[1].name, "test_unyanked"); + assert_eq!(json.crates[1].max_version, "2.0.0"); + assert_eq!(json.crates[1].badges, Some(vec![])); + assert_eq!( + json.crates[1].documentation, + Some("https://unyanked-crate.github.io".to_string()) + ); + assert_eq!( + json.crates[1].homepage, + Some("https://github.com/test/unyanked-crate".to_string()) + ); + assert_eq!( + json.crates[1].description, + Some("unyanked crate".to_string()) + ); +} + #[test] fn yanked_versions_are_not_considered_for_max_version() { let (app, anon, user) = TestApp::init().with_user(); diff --git a/src/views.rs b/src/views.rs index f8edce8f5ac..bce7472e2b6 100644 --- a/src/views.rs +++ b/src/views.rs @@ -245,6 +245,7 @@ impl EncodableCrate { badges: Option>, exact_match: bool, recent_downloads: Option, + all_versions_yanked: bool, ) -> Self { let Crate { name, @@ -256,16 +257,30 @@ impl EncodableCrate { documentation, repository, .. - } = krate; + } = if all_versions_yanked { + Crate::from_yanked(krate) + } else { + krate + }; let versions_link = match versions { Some(..) => None, None => Some(format!("/api/v1/crates/{name}/versions")), }; let keyword_ids = keywords.map(|kws| kws.iter().map(|kw| kw.keyword.clone()).collect()); let category_ids = categories.map(|cats| cats.iter().map(|cat| cat.slug.clone()).collect()); - let badges = badges.map(|_| vec![]); let documentation = Self::remove_blocked_documentation_urls(documentation); + let badges = if all_versions_yanked { + None + } else { + badges.map(|_| vec![]) + }; + let top_versions = if all_versions_yanked { + None + } else { + top_versions + }; + let max_version = top_versions .and_then(|v| v.highest.as_ref()) .map(|v| v.to_string()) @@ -316,6 +331,7 @@ impl EncodableCrate { badges: Option>, exact_match: bool, recent_downloads: Option, + all_versions_yanked: bool, ) -> Self { Self::from( krate, @@ -326,6 +342,7 @@ impl EncodableCrate { badges, exact_match, recent_downloads, + all_versions_yanked, ) }