From 9bc5df2194b55a25c099d850272599a61a092a7b Mon Sep 17 00:00:00 2001 From: Lin Yihai Date: Fri, 1 Dec 2023 23:24:03 +0800 Subject: [PATCH 1/4] Retrive the sort-by value from the "paginate" --- src/web/releases.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/web/releases.rs b/src/web/releases.rs index 572cc7b35..6c2e855d5 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -22,6 +22,8 @@ use axum::{ use base64::{engine::general_purpose::STANDARD as b64, Engine}; use chrono::{DateTime, Utc}; use futures_util::stream::TryStreamExt; +use once_cell::sync::Lazy; +use regex::Regex; use serde::{Deserialize, Serialize}; use sqlx::Row; use std::collections::{BTreeMap, HashMap, HashSet}; @@ -156,7 +158,6 @@ async fn get_search_results( } use crate::utils::APP_USER_AGENT; - use once_cell::sync::Lazy; use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, USER_AGENT}; use reqwest::Client as HttpClient; @@ -498,6 +499,13 @@ impl_axum_webpage! { status = |search| search.status, } +fn retrive_sort_from_paginate(query: &str) -> String { + static RE: Lazy = Lazy::new(|| Regex::new(r"[?&]sort=([^&]+)").unwrap()); + let cap = RE.captures(query).unwrap(); + cap.get(1) + .map_or("relevance".to_string(), |v| v.as_str().to_string()) +} + pub(crate) async fn search_handler( mut conn: DbConnection, Extension(pool): Extension, @@ -509,7 +517,7 @@ pub(crate) async fn search_handler( .get("query") .map(|q| q.to_string()) .unwrap_or_else(|| "".to_string()); - let sort_by = params + let mut sort_by = params .get("sort") .map(|q| q.to_string()) .unwrap_or_else(|| "relevance".to_string()); @@ -578,7 +586,7 @@ pub(crate) async fn search_handler( ); return Err(AxumNope::NoResults); } - + sort_by = retrive_sort_from_paginate(&query_params); get_search_results(&mut conn, &config, &query_params).await? } else if !query.is_empty() { let query_params: String = form_urlencoded::Serializer::new(String::new()) From fa3fb5966e08979f2f047ab9ad2a6311f3cff0a2 Mon Sep 17 00:00:00 2001 From: Lin Yihai Date: Fri, 1 Dec 2023 23:44:52 +0800 Subject: [PATCH 2/4] chore: inline the function `retrive_sort_from_paginate` --- src/web/releases.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/web/releases.rs b/src/web/releases.rs index 6c2e855d5..bf99d162a 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -499,13 +499,6 @@ impl_axum_webpage! { status = |search| search.status, } -fn retrive_sort_from_paginate(query: &str) -> String { - static RE: Lazy = Lazy::new(|| Regex::new(r"[?&]sort=([^&]+)").unwrap()); - let cap = RE.captures(query).unwrap(); - cap.get(1) - .map_or("relevance".to_string(), |v| v.as_str().to_string()) -} - pub(crate) async fn search_handler( mut conn: DbConnection, Extension(pool): Extension, @@ -586,7 +579,14 @@ pub(crate) async fn search_handler( ); return Err(AxumNope::NoResults); } - sort_by = retrive_sort_from_paginate(&query_params); + + static RE: Lazy = Lazy::new(|| Regex::new(r"[?&]sort=([^&]+)").unwrap()); + if let Some(cap) = RE.captures(&query_params) { + sort_by = cap + .get(1) + .map_or("relevance".to_string(), |v| v.as_str().to_string()); + } + get_search_results(&mut conn, &config, &query_params).await? } else if !query.is_empty() { let query_params: String = form_urlencoded::Serializer::new(String::new()) From bdc23d31e3b09e28d1d00b07be24467ef5b2ed1b Mon Sep 17 00:00:00 2001 From: Lin Yihai Date: Sat, 2 Dec 2023 06:29:07 +0800 Subject: [PATCH 3/4] Add testcase to verify "Sort by" select box option value keep the same when use pagination --- src/web/releases.rs | 68 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/src/web/releases.rs b/src/web/releases.rs index bf99d162a..8ae37ac5a 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -23,7 +23,6 @@ use base64::{engine::general_purpose::STANDARD as b64, Engine}; use chrono::{DateTime, Utc}; use futures_util::stream::TryStreamExt; use once_cell::sync::Lazy; -use regex::Regex; use serde::{Deserialize, Serialize}; use sqlx::Row; use std::collections::{BTreeMap, HashMap, HashSet}; @@ -510,7 +509,7 @@ pub(crate) async fn search_handler( .get("query") .map(|q| q.to_string()) .unwrap_or_else(|| "".to_string()); - let mut sort_by = params + let sort_by = params .get("sort") .map(|q| q.to_string()) .unwrap_or_else(|| "relevance".to_string()); @@ -580,13 +579,6 @@ pub(crate) async fn search_handler( return Err(AxumNope::NoResults); } - static RE: Lazy = Lazy::new(|| Regex::new(r"[?&]sort=([^&]+)").unwrap()); - if let Some(cap) = RE.captures(&query_params) { - sort_by = cap - .get(1) - .map_or("relevance".to_string(), |v| v.as_str().to_string()); - } - get_search_results(&mut conn, &config, &query_params).await? } else if !query.is_empty() { let query_params: String = form_urlencoded::Serializer::new(String::new()) @@ -888,6 +880,64 @@ mod tests { }) } + #[test] + fn search_result_can_retrive_sort_by_from_pagination() { + wrapper(|env| { + let mut crates_io = mockito::Server::new(); + env.override_config(|config| { + config.registry_api_host = crates_io.url().parse().unwrap(); + }); + + let web = env.frontend(); + env.fake_release().name("some_random_crate").create()?; + + let _m = crates_io + .mock("GET", "/api/v1/crates") + .match_query(Matcher::AllOf(vec![ + Matcher::UrlEncoded("q".into(), "some_random_crate".into()), + Matcher::UrlEncoded("per_page".into(), "30".into()), + Matcher::UrlEncoded("page".into(), "2".into()), + Matcher::UrlEncoded("sort".into(), "recent-updates".into()), + ])) + .with_status(200) + .with_header("content-type", "application/json") + .with_body( + json!({ + "crates": [ + { "name": "some_random_crate" }, + ], + "meta": { + "next_page": "?q=some_random_crate&sort=recent-updates&per_page=30&page=2", + "prev_page": "?q=some_random_crate&sort=recent-updates&per_page=30&page=1", + } + }) + .to_string(), + ) + .create(); + + // click the "Next Page" Button, the "Sort by" SelectBox should keep the same option. + let next_page_url = format!( + "/releases/search?paginate={}", + b64.encode("?q=some_random_crate&sort=recent-updates&per_page=30&page=2"), + ); + let response = web.get(&next_page_url).send()?; + assert!(response.status().is_success()); + + let page = kuchikiki::parse_html().one(response.text()?); + let is_target_option_selected = page + .select("#nav-sort > option") + .expect("missing option") + .any(|el| { + let attributes = el.attributes.borrow(); + attributes.get("selected").is_some() + && attributes.get("value").unwrap().to_string() == "recent-updates" + }); + assert!(is_target_option_selected); + + Ok(()) + }) + } + #[test] fn search_result_passes_cratesio_pagination_links() { wrapper(|env| { From aa53e621aa31f76f4484cc793eddee59686ded72 Mon Sep 17 00:00:00 2001 From: Lin Yihai Date: Sat, 2 Dec 2023 06:37:50 +0800 Subject: [PATCH 4/4] Use `form_urlencoded::parse` to retrive sort by value --- src/web/releases.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/web/releases.rs b/src/web/releases.rs index 8ae37ac5a..4d1745457 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -509,7 +509,7 @@ pub(crate) async fn search_handler( .get("query") .map(|q| q.to_string()) .unwrap_or_else(|| "".to_string()); - let sort_by = params + let mut sort_by = params .get("sort") .map(|q| q.to_string()) .unwrap_or_else(|| "relevance".to_string()); @@ -579,6 +579,20 @@ pub(crate) async fn search_handler( return Err(AxumNope::NoResults); } + let p = form_urlencoded::parse(query_params.as_bytes()); + if let Some(v) = p + .filter_map(|(k, v)| { + if &k == "sort" { + Some(v.to_string()) + } else { + None + } + }) + .next() + { + sort_by = v; + }; + get_search_results(&mut conn, &config, &query_params).await? } else if !query.is_empty() { let query_params: String = form_urlencoded::Serializer::new(String::new()) @@ -930,7 +944,7 @@ mod tests { .any(|el| { let attributes = el.attributes.borrow(); attributes.get("selected").is_some() - && attributes.get("value").unwrap().to_string() == "recent-updates" + && attributes.get("value").unwrap() == "recent-updates" }); assert!(is_target_option_selected);