Identify users in amplitude via a separate 'metrics_id' UUID

This commit is contained in:
Max Brunsfeld 2022-09-29 12:32:25 -07:00
parent efdedaab53
commit 5d09083a7d
13 changed files with 316 additions and 239 deletions

View File

@ -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();
}
_ => {}

View File

@ -29,7 +29,7 @@ pub struct Telemetry {
#[derive(Default)]
struct TelemetryState {
user_id: Option<Arc<str>>,
metrics_id: Option<Arc<str>>,
device_id: Option<Arc<str>>,
app_version: Option<Arc<str>>,
os_version: Option<Arc<str>>,
@ -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<u64>) {
self.state.lock().user_id = user_id.map(|id| id.to_string().into());
pub fn set_metrics_id(&self, metrics_id: Option<String>) {
self.state.lock().metrics_id = metrics_id.map(|s| s.into());
}
pub fn report_event(self: &Arc<Self>, 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(),

View File

@ -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();
}
}

View File

@ -1,6 +0,0 @@
DROP TABLE signups;
ALTER TABLE users
DROP COLUMN github_user_id;
DROP INDEX index_users_on_email_address;

View File

@ -0,0 +1,2 @@
ALTER TABLE "users"
ADD "metrics_id" uuid NOT NULL DEFAULT gen_random_uuid();

View File

@ -24,6 +24,7 @@ use tracing::instrument;
pub fn routes(rpc_server: &Arc<rpc::Server>, state: Arc<AppState>) -> Router<Body> {
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<B>(req: Request<B>, next: Next<B>) -> 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<AuthenticatedUserParams>,
Extension(app): Extension<Arc<AppState>>,
) -> Result<Json<AuthenticatedUserResponse>> {
let user = app
.db
.get_user_by_github_account(&params.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<i32>,
github_login: Option<String>,
query: Option<String>,
page: Option<u32>,
limit: Option<u32>,
@ -98,14 +122,6 @@ async fn get_users(
Query(params): Query<GetUsersQueryParams>,
Extension(app): Extension<Arc<AppState>>,
) -> Result<Json<Vec<User>>> {
if let Some(github_login) = &params.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<String>,
#[serde(default)]
admin: bool,
#[serde(default)]
invite_count: i32,
}
@ -131,6 +149,7 @@ struct CreateUserParams {
struct CreateUserResponse {
user: User,
signup_device_id: Option<String>,
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(&params.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,
}))
}

View File

@ -17,10 +17,11 @@ pub trait Db: Send + Sync {
email_address: &str,
admin: bool,
params: NewUserParams,
) -> Result<UserId>;
) -> Result<NewUserResult>;
async fn get_all_users(&self, page: u32, limit: u32) -> Result<Vec<User>>;
async fn fuzzy_search_users(&self, query: &str, limit: u32) -> Result<Vec<User>>;
async fn get_user_by_id(&self, id: UserId) -> Result<Option<User>>;
async fn get_user_metrics_id(&self, id: UserId) -> Result<String>;
async fn get_users_by_ids(&self, ids: Vec<UserId>) -> Result<Vec<User>>;
async fn get_users_with_no_invites(&self, invited_by_another_user: bool) -> Result<Vec<User>>;
async fn get_user_by_github_account(
@ -208,21 +209,26 @@ impl Db for PostgresDb {
email_address: &str,
admin: bool,
params: NewUserParams,
) -> Result<UserId> {
) -> Result<NewUserResult> {
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<Vec<User>> {
@ -256,6 +262,18 @@ impl Db for PostgresDb {
Ok(users.into_iter().next())
}
async fn get_user_metrics_id(&self, id: UserId) -> Result<String> {
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<UserId>) -> Result<Vec<User>> {
let ids = ids.into_iter().map(|id| id.0).collect::<Vec<_>>();
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<UserId>,
pub signup_device_id: Option<String>,
}
@ -1808,15 +1828,15 @@ mod test {
email_address: &str,
admin: bool,
params: NewUserParams,
) -> Result<UserId> {
) -> Result<NewUserResult> {
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<Vec<User>> {
@ -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<String> {
Ok("the-metrics-id".to_string())
}
async fn get_users_by_ids(&self, ids: Vec<UserId>) -> Result<Vec<User>> {
self.background.simulate_random_delay().await;
let users = self.users.lock();

View File

@ -12,89 +12,56 @@ async fn test_get_users_by_ids() {
] {
let db = test_db.db();
let user1 = db
.create_user(
"u1@example.com",
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: "u1".into(),
github_user_id: 1,
github_login: format!("user{i}"),
github_user_id: i,
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();
.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",
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: "u1".into(),
github_user_id: 0,
github_login: format!("user{i}"),
github_user_id: i,
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();
.unwrap()
.user_id,
);
}
let project_1 = db.register_project(user_ids[0]).await.unwrap();
db.update_worktree_extensions(
project_1,
1,
@ -281,23 +233,26 @@ 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)])
db.record_user_activity(
t2..t3,
&[(user_ids[1], project_2), (user_ids[0], project_2)],
)
.await
.unwrap();
@ -306,9 +261,9 @@ async fn test_user_activity() {
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)])
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",
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: "u1".into(),
github_user_id: 0,
github_login: format!("user{i}"),
github_user_id: i,
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();
.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<Background> {
Deterministic::new(0).build_background()
}

View File

@ -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));

View File

@ -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<Self>,
request: TypedEnvelope<proto::GetPrivateUserInfo>,
response: Response<proto::GetPrivateUserInfo>,
) -> 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;

View File

@ -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 {

View File

@ -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),