diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index b75be62308..9ec24abae5 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -320,11 +320,9 @@ impl Client { log::info!("set status on client {}: {:?}", self.id, status); let mut state = self.state.write(); *state.status.0.borrow_mut() = status; - let user_id = state.credentials.as_ref().map(|c| c.user_id); match status { Status::Connected { .. } => { - self.telemetry.set_user_id(user_id); state._reconnect_task = None; } Status::ConnectionLost => { @@ -353,7 +351,7 @@ impl Client { })); } Status::SignedOut | Status::UpgradeRequired => { - self.telemetry.set_user_id(user_id); + self.telemetry.set_metrics_id(None); state._reconnect_task.take(); } _ => {} diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 8b7be5ba80..c9b5665e9e 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -29,7 +29,7 @@ pub struct Telemetry { #[derive(Default)] struct TelemetryState { - user_id: Option>, + metrics_id: Option>, device_id: Option>, app_version: Option>, os_version: Option>, @@ -115,7 +115,7 @@ impl Telemetry { flush_task: Default::default(), next_event_id: 0, log_file: None, - user_id: None, + metrics_id: None, }), }); @@ -176,8 +176,8 @@ impl Telemetry { .detach(); } - pub fn set_user_id(&self, user_id: Option) { - self.state.lock().user_id = user_id.map(|id| id.to_string().into()); + pub fn set_metrics_id(&self, metrics_id: Option) { + self.state.lock().metrics_id = metrics_id.map(|s| s.into()); } pub fn report_event(self: &Arc, kind: &str, properties: Value) { @@ -199,7 +199,7 @@ impl Telemetry { None }, user_properties: None, - user_id: state.user_id.clone(), + user_id: state.metrics_id.clone(), device_id: state.device_id.clone(), os_name: state.os_name, os_version: state.os_version.clone(), diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 149d22e77a..b31cda94b3 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -142,10 +142,14 @@ impl UserStore { match status { Status::Connected { .. } => { if let Some((this, user_id)) = this.upgrade(&cx).zip(client.user_id()) { - let user = this + let fetch_user = this .update(&mut cx, |this, cx| this.fetch_user(user_id, cx)) - .log_err() - .await; + .log_err(); + let fetch_metrics_id = + client.request(proto::GetPrivateUserInfo {}).log_err(); + let (user, info) = futures::join!(fetch_user, fetch_metrics_id); + client.telemetry.set_metrics_id(info.map(|i| i.metrics_id)); + client.telemetry.report_event("sign in", Default::default()); current_user_tx.send(user).await.ok(); } } diff --git a/crates/collab/migrations/20220913211150_create_signups.down.sql b/crates/collab/migrations/20220913211150_create_signups.down.sql deleted file mode 100644 index 5504bbb8dc..0000000000 --- a/crates/collab/migrations/20220913211150_create_signups.down.sql +++ /dev/null @@ -1,6 +0,0 @@ -DROP TABLE signups; - -ALTER TABLE users - DROP COLUMN github_user_id; - -DROP INDEX index_users_on_email_address; diff --git a/crates/collab/migrations/20220913211150_create_signups.up.sql b/crates/collab/migrations/20220913211150_create_signups.sql similarity index 100% rename from crates/collab/migrations/20220913211150_create_signups.up.sql rename to crates/collab/migrations/20220913211150_create_signups.sql diff --git a/crates/collab/migrations/20220929182110_add_metrics_id.sql b/crates/collab/migrations/20220929182110_add_metrics_id.sql new file mode 100644 index 0000000000..665d6323bf --- /dev/null +++ b/crates/collab/migrations/20220929182110_add_metrics_id.sql @@ -0,0 +1,2 @@ +ALTER TABLE "users" + ADD "metrics_id" uuid NOT NULL DEFAULT gen_random_uuid(); diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index 0a9d8106ce..08dfa91ba9 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -24,6 +24,7 @@ use tracing::instrument; pub fn routes(rpc_server: &Arc, state: Arc) -> Router { Router::new() + .route("/user", get(get_authenticated_user)) .route("/users", get(get_users).post(create_user)) .route("/users/:id", put(update_user).delete(destroy_user)) .route("/users/:id/access_tokens", post(create_access_token)) @@ -85,10 +86,33 @@ pub async fn validate_api_token(req: Request, next: Next) -> impl IntoR Ok::<_, Error>(next.run(req).await) } +#[derive(Debug, Deserialize)] +struct AuthenticatedUserParams { + github_user_id: i32, + github_login: String, +} + +#[derive(Debug, Serialize)] +struct AuthenticatedUserResponse { + user: User, + metrics_id: String, +} + +async fn get_authenticated_user( + Query(params): Query, + Extension(app): Extension>, +) -> Result> { + let user = app + .db + .get_user_by_github_account(¶ms.github_login, Some(params.github_user_id)) + .await? + .ok_or_else(|| Error::Http(StatusCode::NOT_FOUND, "user not found".into()))?; + let metrics_id = app.db.get_user_metrics_id(user.id).await?; + return Ok(Json(AuthenticatedUserResponse { user, metrics_id })); +} + #[derive(Debug, Deserialize)] struct GetUsersQueryParams { - github_user_id: Option, - github_login: Option, query: Option, page: Option, limit: Option, @@ -98,14 +122,6 @@ async fn get_users( Query(params): Query, Extension(app): Extension>, ) -> Result>> { - if let Some(github_login) = ¶ms.github_login { - let user = app - .db - .get_user_by_github_account(github_login, params.github_user_id) - .await?; - return Ok(Json(Vec::from_iter(user))); - } - let limit = params.limit.unwrap_or(100); let users = if let Some(query) = params.query { app.db.fuzzy_search_users(&query, limit).await? @@ -124,6 +140,8 @@ struct CreateUserParams { email_address: String, email_confirmation_code: Option, #[serde(default)] + admin: bool, + #[serde(default)] invite_count: i32, } @@ -131,6 +149,7 @@ struct CreateUserParams { struct CreateUserResponse { user: User, signup_device_id: Option, + metrics_id: String, } async fn create_user( @@ -143,12 +162,10 @@ async fn create_user( github_user_id: params.github_user_id, invite_count: params.invite_count, }; - let user_id; - let signup_device_id; + // Creating a user via the normal signup process - if let Some(email_confirmation_code) = params.email_confirmation_code { - let result = app - .db + let result = if let Some(email_confirmation_code) = params.email_confirmation_code { + app.db .create_user_from_invite( &Invite { email_address: params.email_address, @@ -156,34 +173,37 @@ async fn create_user( }, user, ) - .await?; - user_id = result.user_id; - signup_device_id = result.signup_device_id; - if let Some(inviter_id) = result.inviting_user_id { - rpc_server - .invite_code_redeemed(inviter_id, user_id) - .await - .trace_err(); - } + .await? } // Creating a user as an admin - else { - user_id = app - .db + else if params.admin { + app.db .create_user(¶ms.email_address, false, user) - .await?; - signup_device_id = None; + .await? + } else { + Err(Error::Http( + StatusCode::UNPROCESSABLE_ENTITY, + "email confirmation code is required".into(), + ))? + }; + + if let Some(inviter_id) = result.inviting_user_id { + rpc_server + .invite_code_redeemed(inviter_id, result.user_id) + .await + .trace_err(); } let user = app .db - .get_user_by_id(user_id) + .get_user_by_id(result.user_id) .await? .ok_or_else(|| anyhow!("couldn't find the user we just created"))?; Ok(Json(CreateUserResponse { user, - signup_device_id, + metrics_id: result.metrics_id, + signup_device_id: result.signup_device_id, })) } diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 8b01cdf971..a12f6a4f89 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -17,10 +17,11 @@ pub trait Db: Send + Sync { email_address: &str, admin: bool, params: NewUserParams, - ) -> Result; + ) -> Result; async fn get_all_users(&self, page: u32, limit: u32) -> Result>; async fn fuzzy_search_users(&self, query: &str, limit: u32) -> Result>; async fn get_user_by_id(&self, id: UserId) -> Result>; + async fn get_user_metrics_id(&self, id: UserId) -> Result; async fn get_users_by_ids(&self, ids: Vec) -> Result>; async fn get_users_with_no_invites(&self, invited_by_another_user: bool) -> Result>; async fn get_user_by_github_account( @@ -208,21 +209,26 @@ impl Db for PostgresDb { email_address: &str, admin: bool, params: NewUserParams, - ) -> Result { + ) -> Result { let query = " INSERT INTO users (email_address, github_login, github_user_id, admin) VALUES ($1, $2, $3, $4) ON CONFLICT (github_login) DO UPDATE SET github_login = excluded.github_login - RETURNING id + RETURNING id, metrics_id::text "; - Ok(sqlx::query_scalar(query) + let (user_id, metrics_id): (UserId, String) = sqlx::query_as(query) .bind(email_address) .bind(params.github_login) .bind(params.github_user_id) .bind(admin) .fetch_one(&self.pool) - .await - .map(UserId)?) + .await?; + Ok(NewUserResult { + user_id, + metrics_id, + signup_device_id: None, + inviting_user_id: None, + }) } async fn get_all_users(&self, page: u32, limit: u32) -> Result> { @@ -256,6 +262,18 @@ impl Db for PostgresDb { Ok(users.into_iter().next()) } + async fn get_user_metrics_id(&self, id: UserId) -> Result { + let query = " + SELECT metrics_id::text + FROM users + WHERE id = $1 + "; + Ok(sqlx::query_scalar(query) + .bind(id) + .fetch_one(&self.pool) + .await?) + } + async fn get_users_by_ids(&self, ids: Vec) -> Result> { let ids = ids.into_iter().map(|id| id.0).collect::>(); let query = " @@ -493,13 +511,13 @@ impl Db for PostgresDb { ))?; } - let user_id: UserId = sqlx::query_scalar( + let (user_id, metrics_id): (UserId, String) = sqlx::query_as( " INSERT INTO users (email_address, github_login, github_user_id, admin, invite_count, invite_code) VALUES ($1, $2, $3, 'f', $4, $5) - RETURNING id + RETURNING id, metrics_id::text ", ) .bind(&invite.email_address) @@ -559,6 +577,7 @@ impl Db for PostgresDb { tx.commit().await?; Ok(NewUserResult { user_id, + metrics_id, inviting_user_id, signup_device_id, }) @@ -1722,6 +1741,7 @@ pub struct NewUserParams { #[derive(Debug)] pub struct NewUserResult { pub user_id: UserId, + pub metrics_id: String, pub inviting_user_id: Option, pub signup_device_id: Option, } @@ -1808,15 +1828,15 @@ mod test { email_address: &str, admin: bool, params: NewUserParams, - ) -> Result { + ) -> Result { self.background.simulate_random_delay().await; let mut users = self.users.lock(); - if let Some(user) = users + let user_id = if let Some(user) = users .values() .find(|user| user.github_login == params.github_login) { - Ok(user.id) + user.id } else { let id = post_inc(&mut *self.next_user_id.lock()); let user_id = UserId(id); @@ -1833,8 +1853,14 @@ mod test { connected_once: false, }, ); - Ok(user_id) - } + user_id + }; + Ok(NewUserResult { + user_id, + metrics_id: "the-metrics-id".to_string(), + inviting_user_id: None, + signup_device_id: None, + }) } async fn get_all_users(&self, _page: u32, _limit: u32) -> Result> { @@ -1850,6 +1876,10 @@ mod test { Ok(self.get_users_by_ids(vec![id]).await?.into_iter().next()) } + async fn get_user_metrics_id(&self, _id: UserId) -> Result { + Ok("the-metrics-id".to_string()) + } + async fn get_users_by_ids(&self, ids: Vec) -> Result> { self.background.simulate_random_delay().await; let users = self.users.lock(); diff --git a/crates/collab/src/db_tests.rs b/crates/collab/src/db_tests.rs index 1e48b4b754..e063b97eb6 100644 --- a/crates/collab/src/db_tests.rs +++ b/crates/collab/src/db_tests.rs @@ -12,89 +12,56 @@ async fn test_get_users_by_ids() { ] { let db = test_db.db(); - let user1 = db - .create_user( - "u1@example.com", - false, - NewUserParams { - github_login: "u1".into(), - github_user_id: 1, - invite_count: 0, - }, - ) - .await - .unwrap(); - let user2 = db - .create_user( - "u2@example.com", - false, - NewUserParams { - github_login: "u2".into(), - github_user_id: 2, - invite_count: 0, - }, - ) - .await - .unwrap(); - let user3 = db - .create_user( - "u3@example.com", - false, - NewUserParams { - github_login: "u3".into(), - github_user_id: 3, - invite_count: 0, - }, - ) - .await - .unwrap(); - let user4 = db - .create_user( - "u4@example.com", - false, - NewUserParams { - github_login: "u4".into(), - github_user_id: 4, - invite_count: 0, - }, - ) - .await - .unwrap(); + let mut user_ids = Vec::new(); + for i in 1..=4 { + user_ids.push( + db.create_user( + &format!("user{i}@example.com"), + false, + NewUserParams { + github_login: format!("user{i}"), + github_user_id: i, + invite_count: 0, + }, + ) + .await + .unwrap() + .user_id, + ); + } assert_eq!( - db.get_users_by_ids(vec![user1, user2, user3, user4]) - .await - .unwrap(), + db.get_users_by_ids(user_ids.clone()).await.unwrap(), vec![ User { - id: user1, - github_login: "u1".to_string(), + id: user_ids[0], + github_login: "user1".to_string(), github_user_id: Some(1), - email_address: Some("u1@example.com".to_string()), + email_address: Some("user1@example.com".to_string()), admin: false, ..Default::default() }, User { - id: user2, - github_login: "u2".to_string(), + id: user_ids[1], + github_login: "user2".to_string(), github_user_id: Some(2), - email_address: Some("u2@example.com".to_string()), + email_address: Some("user2@example.com".to_string()), admin: false, ..Default::default() }, User { - id: user3, - github_login: "u3".to_string(), + id: user_ids[2], + github_login: "user3".to_string(), github_user_id: Some(3), - email_address: Some("u3@example.com".to_string()), + email_address: Some("user3@example.com".to_string()), admin: false, ..Default::default() }, User { - id: user4, - github_login: "u4".to_string(), + id: user_ids[3], + github_login: "user4".to_string(), github_user_id: Some(4), - email_address: Some("u4@example.com".to_string()), + email_address: Some("user4@example.com".to_string()), admin: false, ..Default::default() } @@ -121,7 +88,8 @@ async fn test_get_user_by_github_account() { }, ) .await - .unwrap(); + .unwrap() + .user_id; let user_id2 = db .create_user( "user2@example.com", @@ -133,7 +101,8 @@ async fn test_get_user_by_github_account() { }, ) .await - .unwrap(); + .unwrap() + .user_id; let user = db .get_user_by_github_account("login1", None) @@ -177,7 +146,8 @@ async fn test_worktree_extensions() { }, ) .await - .unwrap(); + .unwrap() + .user_id; let project = db.register_project(user).await.unwrap(); db.update_worktree_extensions(project, 100, Default::default()) @@ -237,43 +207,25 @@ async fn test_user_activity() { let test_db = TestDb::postgres().await; let db = test_db.db(); - let user_1 = db - .create_user( - "u1@example.com", - false, - NewUserParams { - github_login: "u1".into(), - github_user_id: 0, - invite_count: 0, - }, - ) - .await - .unwrap(); - let user_2 = db - .create_user( - "u2@example.com", - false, - NewUserParams { - github_login: "u2".into(), - github_user_id: 0, - invite_count: 0, - }, - ) - .await - .unwrap(); - let user_3 = db - .create_user( - "u3@example.com", - false, - NewUserParams { - github_login: "u3".into(), - github_user_id: 0, - invite_count: 0, - }, - ) - .await - .unwrap(); - let project_1 = db.register_project(user_1).await.unwrap(); + let mut user_ids = Vec::new(); + for i in 0..=2 { + user_ids.push( + db.create_user( + &format!("user{i}@example.com"), + false, + NewUserParams { + github_login: format!("user{i}"), + github_user_id: i, + invite_count: 0, + }, + ) + .await + .unwrap() + .user_id, + ); + } + + let project_1 = db.register_project(user_ids[0]).await.unwrap(); db.update_worktree_extensions( project_1, 1, @@ -281,34 +233,37 @@ async fn test_user_activity() { ) .await .unwrap(); - let project_2 = db.register_project(user_2).await.unwrap(); + let project_2 = db.register_project(user_ids[1]).await.unwrap(); let t0 = OffsetDateTime::now_utc() - Duration::from_secs(60 * 60); // User 2 opens a project let t1 = t0 + Duration::from_secs(10); - db.record_user_activity(t0..t1, &[(user_2, project_2)]) + db.record_user_activity(t0..t1, &[(user_ids[1], project_2)]) .await .unwrap(); let t2 = t1 + Duration::from_secs(10); - db.record_user_activity(t1..t2, &[(user_2, project_2)]) + db.record_user_activity(t1..t2, &[(user_ids[1], project_2)]) .await .unwrap(); // User 1 joins the project let t3 = t2 + Duration::from_secs(10); - db.record_user_activity(t2..t3, &[(user_2, project_2), (user_1, project_2)]) - .await - .unwrap(); + db.record_user_activity( + t2..t3, + &[(user_ids[1], project_2), (user_ids[0], project_2)], + ) + .await + .unwrap(); // User 1 opens another project let t4 = t3 + Duration::from_secs(10); db.record_user_activity( t3..t4, &[ - (user_2, project_2), - (user_1, project_2), - (user_1, project_1), + (user_ids[1], project_2), + (user_ids[0], project_2), + (user_ids[0], project_1), ], ) .await @@ -319,10 +274,10 @@ async fn test_user_activity() { db.record_user_activity( t4..t5, &[ - (user_2, project_2), - (user_1, project_2), - (user_1, project_1), - (user_3, project_1), + (user_ids[1], project_2), + (user_ids[0], project_2), + (user_ids[0], project_1), + (user_ids[2], project_1), ], ) .await @@ -330,13 +285,16 @@ async fn test_user_activity() { // User 2 leaves let t6 = t5 + Duration::from_secs(5); - db.record_user_activity(t5..t6, &[(user_1, project_1), (user_3, project_1)]) - .await - .unwrap(); + db.record_user_activity( + t5..t6, + &[(user_ids[0], project_1), (user_ids[2], project_1)], + ) + .await + .unwrap(); let t7 = t6 + Duration::from_secs(60); let t8 = t7 + Duration::from_secs(10); - db.record_user_activity(t7..t8, &[(user_1, project_1)]) + db.record_user_activity(t7..t8, &[(user_ids[0], project_1)]) .await .unwrap(); @@ -344,8 +302,8 @@ async fn test_user_activity() { db.get_top_users_activity_summary(t0..t6, 10).await.unwrap(), &[ UserActivitySummary { - id: user_1, - github_login: "u1".to_string(), + id: user_ids[0], + github_login: "user0".to_string(), project_activity: vec![ ProjectActivitySummary { id: project_1, @@ -360,8 +318,8 @@ async fn test_user_activity() { ] }, UserActivitySummary { - id: user_2, - github_login: "u2".to_string(), + id: user_ids[1], + github_login: "user1".to_string(), project_activity: vec![ProjectActivitySummary { id: project_2, duration: Duration::from_secs(50), @@ -369,8 +327,8 @@ async fn test_user_activity() { }] }, UserActivitySummary { - id: user_3, - github_login: "u3".to_string(), + id: user_ids[2], + github_login: "user2".to_string(), project_activity: vec![ProjectActivitySummary { id: project_1, duration: Duration::from_secs(15), @@ -442,7 +400,9 @@ async fn test_user_activity() { ); assert_eq!( - db.get_user_activity_timeline(t3..t6, user_1).await.unwrap(), + db.get_user_activity_timeline(t3..t6, user_ids[0]) + .await + .unwrap(), &[ UserActivityPeriod { project_id: project_1, @@ -459,7 +419,9 @@ async fn test_user_activity() { ] ); assert_eq!( - db.get_user_activity_timeline(t0..t8, user_1).await.unwrap(), + db.get_user_activity_timeline(t0..t8, user_ids[0]) + .await + .unwrap(), &[ UserActivityPeriod { project_id: project_2, @@ -501,7 +463,8 @@ async fn test_recent_channel_messages() { }, ) .await - .unwrap(); + .unwrap() + .user_id; let org = db.create_org("org", "org").await.unwrap(); let channel = db.create_org_channel(org, "channel").await.unwrap(); for i in 0..10 { @@ -545,7 +508,8 @@ async fn test_channel_message_nonces() { }, ) .await - .unwrap(); + .unwrap() + .user_id; let org = db.create_org("org", "org").await.unwrap(); let channel = db.create_org_channel(org, "channel").await.unwrap(); @@ -587,7 +551,8 @@ async fn test_create_access_tokens() { }, ) .await - .unwrap(); + .unwrap() + .user_id; db.create_access_token_hash(user, "h1", 3).await.unwrap(); db.create_access_token_hash(user, "h2", 3).await.unwrap(); @@ -678,42 +643,27 @@ async fn test_add_contacts() { ] { let db = test_db.db(); - let user_1 = db - .create_user( - "u1@example.com", - false, - NewUserParams { - github_login: "u1".into(), - github_user_id: 0, - invite_count: 0, - }, - ) - .await - .unwrap(); - let user_2 = db - .create_user( - "u2@example.com", - false, - NewUserParams { - github_login: "u2".into(), - github_user_id: 1, - invite_count: 0, - }, - ) - .await - .unwrap(); - let user_3 = db - .create_user( - "u3@example.com", - false, - NewUserParams { - github_login: "u3".into(), - github_user_id: 2, - invite_count: 0, - }, - ) - .await - .unwrap(); + let mut user_ids = Vec::new(); + for i in 0..3 { + user_ids.push( + db.create_user( + &format!("user{i}@example.com"), + false, + NewUserParams { + github_login: format!("user{i}"), + github_user_id: i, + invite_count: 0, + }, + ) + .await + .unwrap() + .user_id, + ); + } + + let user_1 = user_ids[0]; + let user_2 = user_ids[1]; + let user_3 = user_ids[2]; // User starts with no contacts assert_eq!( @@ -927,12 +877,12 @@ async fn test_add_contacts() { async fn test_invite_codes() { let postgres = TestDb::postgres().await; let db = postgres.db(); - let user1 = db + let NewUserResult { user_id: user1, .. } = db .create_user( - "u1@example.com", + "user1@example.com", false, NewUserParams { - github_login: "u1".into(), + github_login: "user1".into(), github_user_id: 0, invite_count: 0, }, @@ -954,13 +904,14 @@ async fn test_invite_codes() { // User 2 redeems the invite code and becomes a contact of user 1. let user2_invite = db - .create_invite_from_code(&invite_code, "u2@example.com", Some("user-2-device-id")) + .create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id")) .await .unwrap(); let NewUserResult { user_id: user2, inviting_user_id, signup_device_id, + metrics_id, } = db .create_user_from_invite( &user2_invite, @@ -976,6 +927,7 @@ async fn test_invite_codes() { assert_eq!(invite_count, 1); assert_eq!(inviting_user_id, Some(user1)); assert_eq!(signup_device_id.unwrap(), "user-2-device-id"); + assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id); assert_eq!( db.get_contacts(user1).await.unwrap(), [ @@ -1009,13 +961,14 @@ async fn test_invite_codes() { // User 3 redeems the invite code and becomes a contact of user 1. let user3_invite = db - .create_invite_from_code(&invite_code, "u3@example.com", None) + .create_invite_from_code(&invite_code, "user3@example.com", None) .await .unwrap(); let NewUserResult { user_id: user3, inviting_user_id, signup_device_id, + .. } = db .create_user_from_invite( &user3_invite, @@ -1067,7 +1020,7 @@ async fn test_invite_codes() { ); // Trying to reedem the code for the third time results in an error. - db.create_invite_from_code(&invite_code, "u4@example.com", Some("user-4-device-id")) + db.create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id")) .await .unwrap_err(); @@ -1079,7 +1032,7 @@ async fn test_invite_codes() { // User 4 can now redeem the invite code and becomes a contact of user 1. let user4_invite = db - .create_invite_from_code(&invite_code, "u4@example.com", Some("user-4-device-id")) + .create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id")) .await .unwrap(); let user4 = db @@ -1137,7 +1090,7 @@ async fn test_invite_codes() { ); // An existing user cannot redeem invite codes. - db.create_invite_from_code(&invite_code, "u2@example.com", Some("user-2-device-id")) + db.create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id")) .await .unwrap_err(); let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap(); @@ -1232,6 +1185,7 @@ async fn test_signups() { user_id, inviting_user_id, signup_device_id, + .. } = db .create_user_from_invite( &Invite { @@ -1284,6 +1238,51 @@ async fn test_signups() { .unwrap_err(); } +#[tokio::test(flavor = "multi_thread")] +async fn test_metrics_id() { + let postgres = TestDb::postgres().await; + let db = postgres.db(); + + let NewUserResult { + user_id: user1, + metrics_id: metrics_id1, + .. + } = db + .create_user( + "person1@example.com", + false, + NewUserParams { + github_login: "person1".into(), + github_user_id: 101, + invite_count: 5, + }, + ) + .await + .unwrap(); + let NewUserResult { + user_id: user2, + metrics_id: metrics_id2, + .. + } = db + .create_user( + "person2@example.com", + false, + NewUserParams { + github_login: "person2".into(), + github_user_id: 102, + invite_count: 5, + }, + ) + .await + .unwrap(); + + assert_eq!(db.get_user_metrics_id(user1).await.unwrap(), metrics_id1); + assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id2); + assert_eq!(metrics_id1.len(), 36); + assert_eq!(metrics_id2.len(), 36); + assert_ne!(metrics_id1, metrics_id2); +} + fn build_background_executor() -> Arc { Deterministic::new(0).build_background() } diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index 3c9886dc16..e9643d3deb 100644 --- a/crates/collab/src/integration_tests.rs +++ b/crates/collab/src/integration_tests.rs @@ -4663,7 +4663,8 @@ async fn test_random_collaboration( }, ) .await - .unwrap(); + .unwrap() + .user_id; let mut available_guests = vec![ "guest-1".to_string(), "guest-2".to_string(), @@ -4683,7 +4684,8 @@ async fn test_random_collaboration( }, ) .await - .unwrap(); + .unwrap() + .user_id; assert_eq!(*username, format!("guest-{}", guest_user_id)); server .app_state @@ -5206,6 +5208,7 @@ impl TestServer { ) .await .unwrap() + .user_id }; let client_name = name.to_string(); let mut client = cx.read(|cx| Client::new(http.clone(), cx)); diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 5f27352c5a..467ec174ab 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -205,7 +205,8 @@ impl Server { .add_request_handler(Server::follow) .add_message_handler(Server::unfollow) .add_message_handler(Server::update_followers) - .add_request_handler(Server::get_channel_messages); + .add_request_handler(Server::get_channel_messages) + .add_request_handler(Server::get_private_user_info); Arc::new(server) } @@ -1727,6 +1728,20 @@ impl Server { Ok(()) } + async fn get_private_user_info( + self: Arc, + request: TypedEnvelope, + response: Response, + ) -> Result<()> { + let user_id = self + .store() + .await + .user_id_for_connection(request.sender_id)?; + let metrics_id = self.app_state.db.get_user_metrics_id(user_id).await?; + response.send(proto::GetPrivateUserInfoResponse { metrics_id })?; + Ok(()) + } + pub(crate) async fn store(&self) -> StoreGuard<'_> { #[cfg(test)] tokio::task::yield_now().await; diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 7840829b44..6a48ad1b97 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -108,6 +108,9 @@ message Envelope { FollowResponse follow_response = 93; UpdateFollowers update_followers = 94; Unfollow unfollow = 95; + + GetPrivateUserInfo get_private_user_info = 96; + GetPrivateUserInfoResponse get_private_user_info_response = 97; } } @@ -748,6 +751,12 @@ message Unfollow { uint32 leader_id = 2; } +message GetPrivateUserInfo {} + +message GetPrivateUserInfoResponse { + string metrics_id = 1; +} + // Entities message UpdateActiveView { diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 2ba3fa18ba..001753c709 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -167,6 +167,8 @@ messages!( (UpdateProject, Foreground), (UpdateWorktree, Foreground), (UpdateWorktreeExtensions, Background), + (GetPrivateUserInfo, Foreground), + (GetPrivateUserInfoResponse, Foreground), ); request_messages!( @@ -189,6 +191,7 @@ request_messages!( (GetTypeDefinition, GetTypeDefinitionResponse), (GetDocumentHighlights, GetDocumentHighlightsResponse), (GetReferences, GetReferencesResponse), + (GetPrivateUserInfo, GetPrivateUserInfoResponse), (GetProjectSymbols, GetProjectSymbolsResponse), (FuzzySearchUsers, UsersResponse), (GetUsers, UsersResponse),