Content-Length: 20684 | pFad | http://github.com/postgresml/postgresml/pull/1528.diff
thub.com
diff --git a/pgml-dashboard/Cargo.lock b/pgml-dashboard/Cargo.lock
index 59e710ba5..e14050a5b 100644
--- a/pgml-dashboard/Cargo.lock
+++ b/pgml-dashboard/Cargo.lock
@@ -1924,16 +1924,6 @@ version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
-[[package]]
-name = "libloading"
-version = "0.6.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883"
-dependencies = [
- "cfg-if",
- "winapi",
-]
-
[[package]]
name = "libm"
version = "0.2.8"
@@ -2223,47 +2213,6 @@ dependencies = [
"tempfile",
]
-[[package]]
-name = "neon"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28e15415261d880aed48122e917a45e87bb82cf0260bb6db48bbab44b7464373"
-dependencies = [
- "neon-build",
- "neon-macros",
- "neon-runtime",
- "semver 0.9.0",
- "smallvec",
-]
-
-[[package]]
-name = "neon-build"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bac98a702e71804af3dacfde41edde4a16076a7bbe889ae61e56e18c5b1c811"
-
-[[package]]
-name = "neon-macros"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7288eac8b54af7913c60e0eb0e2a7683020dffa342ab3fd15e28f035ba897cf"
-dependencies = [
- "quote",
- "syn 1.0.109",
- "syn-mid",
-]
-
-[[package]]
-name = "neon-runtime"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4676720fa8bb32c64c3d9f49c47a47289239ec46b4bdb66d0913cc512cb0daca"
-dependencies = [
- "cfg-if",
- "libloading",
- "smallvec",
-]
-
[[package]]
name = "new_debug_unreachable"
version = "1.0.4"
@@ -2586,7 +2535,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pgml"
-version = "1.0.4"
+version = "1.1.0"
dependencies = [
"anyhow",
"async-trait",
@@ -2605,7 +2554,6 @@ dependencies = [
"parking_lot",
"regex",
"reqwest",
- "rust_bridge",
"sea-query",
"sea-query-binder",
"serde",
@@ -3308,31 +3256,6 @@ dependencies = [
"serde_derive",
]
-[[package]]
-name = "rust_bridge"
-version = "0.1.0"
-dependencies = [
- "rust_bridge_macros",
- "rust_bridge_traits",
-]
-
-[[package]]
-name = "rust_bridge_macros"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "proc-macro2",
- "quote",
- "syn 2.0.32",
-]
-
-[[package]]
-name = "rust_bridge_traits"
-version = "0.1.0"
-dependencies = [
- "neon",
-]
-
[[package]]
name = "rustc-demangle"
version = "0.1.23"
@@ -3351,7 +3274,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
- "semver 1.0.18",
+ "semver",
]
[[package]]
@@ -3616,27 +3539,12 @@ dependencies = [
"smallvec",
]
-[[package]]
-name = "semver"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
-dependencies = [
- "semver-parser",
-]
-
[[package]]
name = "semver"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
-[[package]]
-name = "semver-parser"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
-
[[package]]
name = "sentry"
version = "0.31.5"
@@ -4332,17 +4240,6 @@ dependencies = [
"unicode-ident",
]
-[[package]]
-name = "syn-mid"
-version = "0.5.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fea305d57546cc8cd04feb14b62ec84bf17f50e3f7b12560d7bfa9265f39d9ed"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
[[package]]
name = "sync_wrapper"
version = "0.1.2"
diff --git a/pgml-dashboard/src/lib.rs b/pgml-dashboard/src/lib.rs
index 6b5efceb2..3bdc0ecdd 100644
--- a/pgml-dashboard/src/lib.rs
+++ b/pgml-dashboard/src/lib.rs
@@ -390,7 +390,6 @@ pub fn replace_banner_product(
context: &Cluster,
) -> Result {
let mut all_notification_cookies = Notifications::get_viewed(cookies);
-
let current_notification_cookie = all_notification_cookies.iter().position(|x| x.id == id);
match current_notification_cookie {
@@ -408,8 +407,8 @@ pub fn replace_banner_product(
Notifications::update_viewed(&all_notification_cookies, cookies);
- // Get the notification that triggered this call.
- // Guaranteed to exist since it built the component that called this, so this is safe to unwrap.
+ // Get the notification that triggered this call..
+ // unwrap notifications if fine since we should panic if this is missing.
let last_notification = context
.notifications
.as_ref()
@@ -424,11 +423,15 @@ pub fn replace_banner_product(
.into_iter()
.filter(|n: &Notification| -> bool {
let n = n.clone().set_viewed(n.id == id);
- Notification::product_filter(
- &n,
- last_notification.clone().unwrap().level.clone(),
- deployment_id.clone(),
- )
+ if last_notification.clone().is_none() {
+ return false;
+ } else {
+ Notification::product_filter(
+ &n,
+ last_notification.clone().unwrap().level.clone(),
+ deployment_id.clone(),
+ )
+ }
})
.next(),
_ => None,
@@ -519,3 +522,188 @@ pub fn routes() -> Vec {
pub async fn migrate(pool: &PgPool) -> anyhow::Result<()> {
Ok(sqlx::migrate!("./migrations").run(pool).await?)
}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::components::sections::footers::MarketingFooter;
+ use crate::guards::Cluster;
+ use rocket::fairing::AdHoc;
+ use rocket::http::Cookie;
+ use rocket::local::asynchronous::Client;
+
+ #[sqlx::test]
+ async fn test_remove_modal() {
+ let rocket = rocket::build().mount("/", routes());
+ let client = Client::untracked(rocket).await.unwrap();
+
+ let cookie = vec![
+ NotificationCookie {
+ id: "1".to_string(),
+ time_viewed: Some(chrono::Utc::now() - chrono::Duration::days(1)),
+ time_modal_viewed: Some(chrono::Utc::now() - chrono::Duration::days(1)),
+ },
+ NotificationCookie {
+ id: "2".to_string(),
+ time_viewed: None,
+ time_modal_viewed: None,
+ },
+ ];
+
+ let response = client
+ .get("/notifications/product/modal/remove_modal?id=1")
+ .private_cookie(Cookie::new("session", Notifications::safe_serialize_session(&cookie)))
+ .dispatch()
+ .await;
+
+ let time_modal_viewed = Notifications::get_viewed(response.cookies())
+ .get(0)
+ .unwrap()
+ .time_modal_viewed;
+
+ // Update modal view time for existing notification cookie
+ assert_eq!(time_modal_viewed.is_some(), true);
+
+ let response = client
+ .get("/notifications/product/modal/remove_modal?id=3")
+ .private_cookie(Cookie::new("session", Notifications::safe_serialize_session(&cookie)))
+ .dispatch()
+ .await;
+
+ let time_modal_viewed = Notifications::get_viewed(response.cookies())
+ .get(0)
+ .unwrap()
+ .time_modal_viewed;
+
+ // Update modal view time for new notification cookie
+ assert_eq!(time_modal_viewed.is_some(), true);
+ }
+
+ #[sqlx::test]
+ async fn test_remove_banner_product() {
+ let rocket = rocket::build().mount("/", routes());
+ let client = Client::untracked(rocket).await.unwrap();
+
+ let cookie = vec![
+ NotificationCookie {
+ id: "1".to_string(),
+ time_viewed: Some(chrono::Utc::now() - chrono::Duration::days(1)),
+ time_modal_viewed: Some(chrono::Utc::now() - chrono::Duration::days(1)),
+ },
+ NotificationCookie {
+ id: "2".to_string(),
+ time_viewed: None,
+ time_modal_viewed: Some(chrono::Utc::now() - chrono::Duration::days(1)),
+ },
+ ];
+
+ let response = client
+ .get("/notifications/product/remove_banner?id=1&target=ajskghjfbs")
+ .private_cookie(Cookie::new("session", Notifications::safe_serialize_session(&cookie)))
+ .dispatch()
+ .await;
+
+ let time_viewed = Notifications::get_viewed(response.cookies())
+ .get(0)
+ .unwrap()
+ .time_viewed;
+
+ // Update view time for existing notification cookie
+ assert_eq!(time_viewed.is_some(), true);
+
+ let response = client
+ .get("/notifications/product/remove_banner?id=3&target=ajfadghs")
+ .private_cookie(Cookie::new("session", Notifications::safe_serialize_session(&cookie)))
+ .dispatch()
+ .await;
+
+ let time_viewed = Notifications::get_viewed(response.cookies())
+ .get(0)
+ .unwrap()
+ .time_viewed;
+
+ // Update view time for new notification cookie
+ assert_eq!(time_viewed.is_some(), true);
+ }
+
+ #[sqlx::test]
+ async fn test_replace_banner_product() {
+ let notification1 = Notification::new("Test notification 1")
+ .set_level(&NotificationLevel::ProductMedium)
+ .set_deployment("1");
+ let notification2 = Notification::new("Test notification 2")
+ .set_level(&NotificationLevel::ProductMedium)
+ .set_deployment("1");
+ let _notification3 = Notification::new("Test notification 3")
+ .set_level(&NotificationLevel::ProductMedium)
+ .set_deployment("2");
+ let _notification4 = Notification::new("Test notification 4").set_level(&NotificationLevel::ProductMedium);
+ let _notification5 = Notification::new("Test notification 5").set_level(&NotificationLevel::ProductMarketing);
+
+ let rocket = rocket::build()
+ .attach(AdHoc::on_request("request", |req, _| {
+ Box::pin(async {
+ req.local_cache(|| Cluster {
+ pool: None,
+ context: Context {
+ user: models::User::default(),
+ cluster: models::Cluster::default(),
+ dropdown_nav: StaticNav { links: vec![] },
+ product_left_nav: StaticNav { links: vec![] },
+ marketing_footer: MarketingFooter::new().render_once().unwrap(),
+ head_items: None,
+ },
+ notifications: Some(vec![
+ Notification::new("Test notification 1")
+ .set_level(&NotificationLevel::ProductMedium)
+ .set_deployment("1"),
+ Notification::new("Test notification 2")
+ .set_level(&NotificationLevel::ProductMedium)
+ .set_deployment("1"),
+ Notification::new("Test notification 3")
+ .set_level(&NotificationLevel::ProductMedium)
+ .set_deployment("2"),
+ Notification::new("Test notification 4").set_level(&NotificationLevel::ProductMedium),
+ Notification::new("Test notification 5").set_level(&NotificationLevel::ProductMarketing),
+ ]),
+ });
+ })
+ }))
+ .mount("/", routes());
+
+ let client = Client::tracked(rocket).await.unwrap();
+
+ let response = client
+ .get(format!(
+ "/notifications/product/replace_banner?id={}&deployment_id=1",
+ notification1.id
+ ))
+ .dispatch()
+ .await;
+
+ let body = response.into_string().await.unwrap();
+ let rsp_contains_next_notification = body.contains("Test notification 2");
+
+ // Ensure the banner is replaced with next notification of same type
+ assert_eq!(rsp_contains_next_notification, true);
+
+ let response = client
+ .get(format!(
+ "/notifications/product/replace_banner?id={}&deployment_id=1",
+ notification2.id
+ ))
+ .dispatch()
+ .await;
+
+ let body = response.into_string().await.unwrap();
+ let rsp_contains_next_notification_3 = body.contains("Test notification 3");
+ let rsp_contains_next_notification_4 = body.contains("Test notification 4");
+ let rsp_contains_next_notification_5 = body.contains("Test notification 5");
+
+ // Ensure the next notification is not found since none match deployment id or level
+ assert_eq!(
+ rsp_contains_next_notification_3 && rsp_contains_next_notification_4 && rsp_contains_next_notification_5,
+ false
+ );
+ }
+}
diff --git a/pgml-dashboard/src/main.rs b/pgml-dashboard/src/main.rs
index a2e4fb90c..b31f488e4 100644
--- a/pgml-dashboard/src/main.rs
+++ b/pgml-dashboard/src/main.rs
@@ -137,7 +137,7 @@ mod test {
async fn rocket() -> Rocket {
dotenv::dotenv().ok();
- pgml_dashboard::migrate(Cluster::default(None).pool()).await.unwrap();
+ pgml_dashboard::migrate(Cluster::default().pool()).await.unwrap();
let mut site_search = markdown::SiteSearch::new()
.await
diff --git a/pgml-dashboard/src/utils/cookies.rs b/pgml-dashboard/src/utils/cookies.rs
index e7e5be1d5..370e3d4b1 100644
--- a/pgml-dashboard/src/utils/cookies.rs
+++ b/pgml-dashboard/src/utils/cookies.rs
@@ -2,55 +2,123 @@ use chrono;
use rocket::http::{Cookie, CookieJar};
use rocket::serde::{Deserialize, Serialize};
-#[derive(Debug, Clone, Deserialize, Serialize)]
+#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)]
pub struct NotificationCookie {
pub id: String,
pub time_viewed: Option>,
pub time_modal_viewed: Option>,
}
-impl std::fmt::Display for NotificationCookie {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- let mut rsp = format!(r#"{{"id": "{}""#, self.id.clone());
- if self.time_viewed.is_some() {
- rsp.push_str(&format!(r#", "time_viewed": "{}""#, self.time_viewed.clone().unwrap()));
- }
- if self.time_modal_viewed.is_some() {
- rsp.push_str(&format!(
- r#", "time_modal_viewed": "{}""#,
- self.time_modal_viewed.clone().unwrap()
- ));
- }
- rsp.push_str("}");
- return write!(f, "{}", rsp);
- }
-}
-
pub struct Notifications {}
impl Notifications {
- pub fn update_viewed(new: &Vec, cookies: &CookieJar<'_>) {
- let serialized = new.iter().map(|x| x.to_string()).collect::>();
+ pub fn update_viewed(all_desired_notifications: &Vec, cookies: &CookieJar<'_>) {
+ let session = Notifications::safe_serialize_session(all_desired_notifications);
- let mut cookie = Cookie::new("session", format!(r#"{{"notifications": [{}]}}"#, serialized.join(",")));
+ let mut cookie = Cookie::new("session", session);
cookie.set_max_age(::time::Duration::weeks(4));
cookies.add_private(cookie);
}
pub fn get_viewed(cookies: &CookieJar<'_>) -> Vec {
- let viewed: Vec = match cookies.get_private("session") {
- Some(session) => {
- match serde_json::from_str::(session.value()).unwrap()["notifications"].as_array() {
- Some(items) => items
- .into_iter()
- .map(|x| serde_json::from_str::(&x.to_string()).unwrap())
- .collect::>(),
- _ => vec![],
- }
- }
+ match cookies.get_private("session") {
+ Some(session) => Notifications::safe_deserialize_session(session.value()),
None => vec![],
- };
+ }
+ }
+
+ pub fn safe_deserialize_session(session: &str) -> Vec {
+ match serde_json::from_str::(session).unwrap_or_else(|_| {
+ serde_json::from_str::(&Notifications::safe_serialize_session(&vec![])).unwrap()
+ })["notifications"]
+ .as_array()
+ {
+ Some(items) => items
+ .into_iter()
+ .map(|notification| {
+ serde_json::from_str::(¬ification.to_string()).unwrap_or_else(|_| {
+ serde_json::from_str::(¬ification.to_string())
+ .and_then(|id| {
+ Ok(NotificationCookie {
+ id,
+ time_viewed: None,
+ time_modal_viewed: None,
+ })
+ })
+ .unwrap_or_else(|_| NotificationCookie::default())
+ })
+ })
+ .collect::>(),
+ _ => vec![],
+ }
+ }
+
+ pub fn safe_serialize_session(cookies: &Vec) -> String {
+ let serialized = cookies
+ .iter()
+ .map(|x| serde_json::to_string(x))
+ .filter(|x| x.is_ok())
+ .map(|x| x.unwrap())
+ .collect::>();
+
+ format!(r#"{{"notifications": [{}]}}"#, serialized.join(","))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ // Test that we can safely deserialize expected session data.
+ #[test]
+ fn test_safe_deserialize_session() {
+ let session = r#"{"notifications": [{"id": "1", "time_viewed": null, "time_modal_viewed": null}, {"id": "1234567891234", "time_viewed": "2021-08-01T00:00:00Z"}]}"#;
+ let expected = vec![
+ NotificationCookie {
+ id: "1".to_string(),
+ time_viewed: None,
+ time_modal_viewed: None,
+ },
+ NotificationCookie {
+ id: "1234567891234".to_string(),
+ time_viewed: Some(
+ chrono::DateTime::parse_from_rfc3339("2021-08-01T00:00:00Z")
+ .unwrap()
+ .into(),
+ ),
+ time_modal_viewed: None,
+ },
+ ];
+ assert_eq!(Notifications::safe_deserialize_session(session), expected);
+ }
+
+ // Test that new notification system is backwards compatible.
+ #[test]
+ fn test_safe_deserialize_session_old_form() {
+ let session = r#"{"notifications": ["123456789"]}"#;
+ let expected = vec![NotificationCookie {
+ id: "123456789".to_string(),
+ time_viewed: None,
+ time_modal_viewed: None,
+ }];
+ assert_eq!(Notifications::safe_deserialize_session(session), expected);
+ }
+
+ #[test]
+ fn test_safe_deserialize_session_empty() {
+ let session = r#"{}"#;
+ let expected: Vec = vec![];
+ assert_eq!(Notifications::safe_deserialize_session(session), expected);
+ }
- viewed
+ #[test]
+ fn test_safe_serialize_session() {
+ let cookies = vec![NotificationCookie {
+ id: "1".to_string(),
+ time_viewed: None,
+ time_modal_viewed: None,
+ }];
+ let expected = r#"{"notifications": [{"id":"1","time_viewed":null,"time_modal_viewed":null}]}"#;
+ assert_eq!(Notifications::safe_serialize_session(&cookies), expected);
}
}
--- a PPN by Garber Painting Akron. With Image Size Reduction included!Fetched URL: http://github.com/postgresml/postgresml/pull/1528.diff
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy