From a27a17b8e2c0a53b1585898935f932e9f5127ab9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Sep 2021 16:05:34 +0200 Subject: [PATCH] Make scrolling up in chat panel smoother This increases the threshold at which we start loading new messages as well as the amount of messages we get back from the server every time we fetch. Also, we restructured the seed binary to use the methods in `Db` to generate seed data and added random chat messages. Co-Authored-By: Nathan Sobo --- Cargo.lock | 11 ++++ script/seed-db | 2 +- server/Cargo.toml | 10 ++- server/src/bin/seed.rs | 144 ++++++++++++++++++----------------------- server/src/db.rs | 128 ++++++++++++++++++++++++++---------- server/src/rpc.rs | 9 +-- zed/src/chat_panel.rs | 4 +- 7 files changed, 183 insertions(+), 125 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3f041b7acdbd347ffc44b040fd5c9eedb93b6d81..fdb5a30352589b0fe0664c648b721c38d631a2df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2736,6 +2736,16 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "lipsum" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ee7271c76a89032dcc7e595c0a739a9c5514eab483deb0e82981fe2098c56a" +dependencies = [ + "rand 0.8.3", + "rand_chacha 0.3.0", +] + [[package]] name = "lock_api" version = "0.4.2" @@ -5872,6 +5882,7 @@ dependencies = [ "http-auth-basic", "jwt-simple", "lazy_static", + "lipsum", "oauth2", "oauth2-surf", "parking_lot", diff --git a/script/seed-db b/script/seed-db index 0bdfc0a5e33d839bb09e1a5884d598dde1c9a5a5..8ccac320a5017d578ac2f58ba8e41c87a7c007c3 100755 --- a/script/seed-db +++ b/script/seed-db @@ -2,4 +2,4 @@ set -e cd server -cargo run --bin seed +cargo run --features seed-dependencies --bin seed diff --git a/server/Cargo.toml b/server/Cargo.toml index aad43e5b6e25805ef53d06a85823f66ede415c17..fec5bec74d30c439652dae44bb38375303131ddc 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -5,6 +5,10 @@ edition = "2018" name = "zed-server" version = "0.1.0" +[[bin]] +name = "seed" +required-features = ["seed-support"] + [dependencies] anyhow = "1.0.40" async-std = { version = "1.8.0", features = ["attributes"] } @@ -19,6 +23,7 @@ futures = "0.3" handlebars = "3.5" http-auth-basic = "0.1.3" jwt-simple = "0.10.0" +lipsum = { version = "0.8", optional = true } oauth2 = { version = "4.0.0", default_features = false } oauth2-surf = "0.1.1" parking_lot = "0.11.1" @@ -46,6 +51,9 @@ features = ["runtime-async-std-rustls", "postgres", "time"] [dev-dependencies] gpui = { path = "../gpui" } -zed = { path = "../zed", features = ["test-support"] } lazy_static = "1.4" serde_json = { version = "1.0.64", features = ["preserve_order"] } +zed = { path = "../zed", features = ["test-support"] } + +[features] +seed-support = ["lipsum"] diff --git a/server/src/bin/seed.rs b/server/src/bin/seed.rs index 61af3bcd88a261cb27609d725299aae0e9dc550e..b259dc4c14b24ea8b1278be56a6610f2e5fa1f64 100644 --- a/server/src/bin/seed.rs +++ b/server/src/bin/seed.rs @@ -1,6 +1,11 @@ -use sqlx::postgres::PgPoolOptions; +use db::{Db, UserId}; +use rand::prelude::*; use tide::log; +use time::{Duration, OffsetDateTime}; +#[allow(unused)] +#[path = "../db.rs"] +mod db; #[path = "../env.rs"] mod env; @@ -13,95 +18,74 @@ async fn main() { ); } + let mut rng = StdRng::from_entropy(); let database_url = std::env::var("DATABASE_URL").expect("missing DATABASE_URL env var"); - let db = PgPoolOptions::new() - .max_connections(5) - .connect(&database_url) + let db = Db::new(&database_url, 5) .await .expect("failed to connect to postgres database"); let zed_users = ["nathansobo", "maxbrunsfeld", "as-cii", "iamnbutler"]; - let mut zed_user_ids = Vec::::new(); + let mut zed_user_ids = Vec::::new(); for zed_user in zed_users { - zed_user_ids.push( - sqlx::query_scalar( - r#" - INSERT INTO users - (github_login, admin) - VALUES - ($1, true) - ON CONFLICT (github_login) DO UPDATE SET - github_login=EXCLUDED.github_login - RETURNING id - "#, - ) - .bind(zed_user) - .fetch_one(&db) - .await - .expect("failed to insert user"), - ) + if let Some(user_id) = db.get_user(zed_user).await.expect("failed to fetch user") { + zed_user_ids.push(user_id); + } else { + zed_user_ids.push( + db.create_user(zed_user, true) + .await + .expect("failed to insert user"), + ); + } } - let zed_org_id: i32 = sqlx::query_scalar( - r#" - INSERT INTO orgs - (name, slug) - VALUES - ('Zed', 'zed') - ON CONFLICT (slug) DO UPDATE SET - slug=EXCLUDED.slug - RETURNING id - "#, - ) - .fetch_one(&db) - .await - .expect("failed to insert org"); - - let general_channel_id: i32 = sqlx::query_scalar( - r#" - INSERT INTO channels - (owner_is_user, owner_id, name) - VALUES - (false, $1, 'General') - ON CONFLICT (owner_is_user, owner_id, name) DO UPDATE SET - name=EXCLUDED.name - RETURNING id - "#, - ) - .bind(zed_org_id) - .fetch_one(&db) - .await - .expect("failed to insert channel"); - - for user_id in zed_user_ids { - sqlx::query( - r#" - INSERT INTO org_memberships - (org_id, user_id, admin) - VALUES - ($1, $2, true) - ON CONFLICT DO NOTHING - "#, - ) - .bind(zed_org_id) - .bind(user_id) - .execute(&db) + let zed_org_id = if let Some(org) = db + .find_org_by_slug("zed") .await - .expect("failed to insert org membership"); + .expect("failed to fetch org") + { + org.id + } else { + db.create_org("Zed", "zed") + .await + .expect("failed to insert org") + }; - sqlx::query( - r#" - INSERT INTO channel_memberships - (channel_id, user_id, admin) - VALUES - ($1, $2, true) - ON CONFLICT DO NOTHING - "#, - ) - .bind(general_channel_id) - .bind(user_id) - .execute(&db) + let general_channel_id = if let Some(channel) = db + .get_org_channels(zed_org_id) .await - .expect("failed to insert channel membership"); + .expect("failed to fetch channels") + .iter() + .find(|c| c.name == "General") + { + channel.id + } else { + let channel_id = db + .create_org_channel(zed_org_id, "General") + .await + .expect("failed to insert channel"); + + let now = OffsetDateTime::now_utc(); + let max_seconds = Duration::days(100).as_seconds_f64(); + let mut timestamps = (0..1000) + .map(|_| now - Duration::seconds_f64(rng.gen_range(0_f64..=max_seconds))) + .collect::>(); + timestamps.sort(); + for timestamp in timestamps { + let sender_id = *zed_user_ids.choose(&mut rng).unwrap(); + let body = lipsum::lipsum_words(rng.gen_range(1..=50)); + db.create_channel_message(channel_id, sender_id, &body, timestamp) + .await + .expect("failed to insert message"); + } + channel_id + }; + + for user_id in zed_user_ids { + db.add_org_member(zed_org_id, user_id, true) + .await + .expect("failed to insert org membership"); + db.add_channel_member(general_channel_id, user_id, true) + .await + .expect("failed to insert channel membership"); } } diff --git a/server/src/db.rs b/server/src/db.rs index 374761a73ce181a380bab3492f98fca0c293a73e..62797f6b81b414410a0ca238545e01b47716da4e 100644 --- a/server/src/db.rs +++ b/server/src/db.rs @@ -27,35 +27,6 @@ pub struct Db { test_mode: bool, } -#[derive(Debug, FromRow, Serialize)] -pub struct User { - pub id: UserId, - pub github_login: String, - pub admin: bool, -} - -#[derive(Debug, FromRow, Serialize)] -pub struct Signup { - pub id: SignupId, - pub github_login: String, - pub email_address: String, - pub about: String, -} - -#[derive(Debug, FromRow, Serialize)] -pub struct Channel { - pub id: ChannelId, - pub name: String, -} - -#[derive(Debug, FromRow)] -pub struct ChannelMessage { - pub id: MessageId, - pub sender_id: UserId, - pub body: String, - pub sent_at: OffsetDateTime, -} - impl Db { pub async fn new(url: &str, max_connections: u32) -> tide::Result { let pool = DbOptions::new() @@ -113,6 +84,22 @@ impl Db { // users + #[allow(unused)] // Help rust-analyzer + #[cfg(any(test, feature = "seed-support"))] + pub async fn get_user(&self, github_login: &str) -> Result> { + test_support!(self, { + let query = " + SELECT id + FROM users + WHERE github_login = $1 + "; + sqlx::query_scalar(query) + .bind(github_login) + .fetch_optional(&self.pool) + .await + }) + } + pub async fn create_user(&self, github_login: &str, admin: bool) -> Result { test_support!(self, { let query = " @@ -231,7 +218,23 @@ impl Db { // orgs - #[cfg(test)] + #[allow(unused)] // Help rust-analyzer + #[cfg(any(test, feature = "seed-support"))] + pub async fn find_org_by_slug(&self, slug: &str) -> Result> { + test_support!(self, { + let query = " + SELECT * + FROM orgs + WHERE slug = $1 + "; + sqlx::query_as(query) + .bind(slug) + .fetch_optional(&self.pool) + .await + }) + } + + #[cfg(any(test, feature = "seed-support"))] pub async fn create_org(&self, name: &str, slug: &str) -> Result { test_support!(self, { let query = " @@ -248,7 +251,7 @@ impl Db { }) } - #[cfg(test)] + #[cfg(any(test, feature = "seed-support"))] pub async fn add_org_member( &self, org_id: OrgId, @@ -259,6 +262,7 @@ impl Db { let query = " INSERT INTO org_memberships (org_id, user_id, admin) VALUES ($1, $2, $3) + ON CONFLICT DO NOTHING "; sqlx::query(query) .bind(org_id.0) @@ -272,7 +276,7 @@ impl Db { // channels - #[cfg(test)] + #[cfg(any(test, feature = "seed-support"))] pub async fn create_org_channel(&self, org_id: OrgId, name: &str) -> Result { test_support!(self, { let query = " @@ -289,7 +293,25 @@ impl Db { }) } - pub async fn get_channels_for_user(&self, user_id: UserId) -> Result> { + #[allow(unused)] // Help rust-analyzer + #[cfg(any(test, feature = "seed-support"))] + pub async fn get_org_channels(&self, org_id: OrgId) -> Result> { + test_support!(self, { + let query = " + SELECT * + FROM channels + WHERE + channels.owner_is_user = false AND + channels.owner_id = $1 + "; + sqlx::query_as(query) + .bind(org_id.0) + .fetch_all(&self.pool) + .await + }) + } + + pub async fn get_accessible_channels(&self, user_id: UserId) -> Result> { test_support!(self, { let query = " SELECT @@ -328,7 +350,7 @@ impl Db { }) } - #[cfg(test)] + #[cfg(any(test, feature = "seed-support"))] pub async fn add_channel_member( &self, channel_id: ChannelId, @@ -339,6 +361,7 @@ impl Db { let query = " INSERT INTO channel_memberships (channel_id, user_id, admin) VALUES ($1, $2, $3) + ON CONFLICT DO NOTHING "; sqlx::query(query) .bind(channel_id.0) @@ -432,10 +455,45 @@ macro_rules! id_type { } id_type!(UserId); +#[derive(Debug, FromRow, Serialize)] +pub struct User { + pub id: UserId, + pub github_login: String, + pub admin: bool, +} + id_type!(OrgId); -id_type!(ChannelId); +#[derive(FromRow)] +pub struct Org { + pub id: OrgId, + pub name: String, + pub slug: String, +} + id_type!(SignupId); +#[derive(Debug, FromRow, Serialize)] +pub struct Signup { + pub id: SignupId, + pub github_login: String, + pub email_address: String, + pub about: String, +} + +id_type!(ChannelId); +#[derive(Debug, FromRow, Serialize)] +pub struct Channel { + pub id: ChannelId, + pub name: String, +} + id_type!(MessageId); +#[derive(Debug, FromRow)] +pub struct ChannelMessage { + pub id: MessageId, + pub sender_id: UserId, + pub body: String, + pub sent_at: OffsetDateTime, +} #[cfg(test)] pub mod tests { diff --git a/server/src/rpc.rs b/server/src/rpc.rs index 8a7f2d43af07be09b32aaa7aa072c4888e7e41f9..2328f588714f111b85795bc12ed52bcf91a6152a 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -77,12 +77,7 @@ struct Channel { connection_ids: HashSet, } -#[cfg(debug_assertions)] -const MESSAGE_COUNT_PER_PAGE: usize = 10; - -#[cfg(not(debug_assertions))] -const MESSAGE_COUNT_PER_PAGE: usize = 50; - +const MESSAGE_COUNT_PER_PAGE: usize = 100; const MAX_MESSAGE_LEN: usize = 1024; impl Server { @@ -528,7 +523,7 @@ impl Server { .read() .await .user_id_for_connection(request.sender_id)?; - let channels = self.app_state.db.get_channels_for_user(user_id).await?; + let channels = self.app_state.db.get_accessible_channels(user_id).await?; self.peer .respond( request.receipt(), diff --git a/zed/src/chat_panel.rs b/zed/src/chat_panel.rs index 1b757d3ea4183d3466be23471a8ba005c2478fbc..6edac94b2ec61b6cb6ec1ef35eb8dadd3998270b 100644 --- a/zed/src/chat_panel.rs +++ b/zed/src/chat_panel.rs @@ -16,6 +16,8 @@ use gpui::{ use postage::watch; use time::{OffsetDateTime, UtcOffset}; +const MESSAGE_LOADING_THRESHOLD: usize = 50; + pub struct ChatPanel { channel_list: ModelHandle, active_channel: Option<(ModelHandle, Subscription)>, @@ -80,7 +82,7 @@ impl ChatPanel { } }); message_list.set_scroll_handler(|visible_range, cx| { - if visible_range.start < 5 { + if visible_range.start < MESSAGE_LOADING_THRESHOLD { cx.dispatch_action(LoadMoreMessages); } });