diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index b5e4558155..3d85aea3c5 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -14,9 +14,11 @@ use async_tungstenite::tungstenite::{ }; use futures::{future::LocalBoxFuture, FutureExt, SinkExt, StreamExt, TryStreamExt}; use gpui::{ - actions, serde_json::Value, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, - AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, - MutableAppContext, Task, View, ViewContext, ViewHandle, + actions, + serde_json::{json, Value}, + AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext, + AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, View, ViewContext, + ViewHandle, }; use http::HttpClient; use lazy_static::lazy_static; @@ -52,13 +54,29 @@ lazy_static! { pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894"; -actions!(client, [Authenticate]); +actions!(client, [Authenticate, TestTelemetry]); -pub fn init(rpc: Arc, cx: &mut MutableAppContext) { - cx.add_global_action(move |_: &Authenticate, cx| { - let rpc = rpc.clone(); - cx.spawn(|cx| async move { rpc.authenticate_and_connect(true, &cx).log_err().await }) +pub fn init(client: Arc, cx: &mut MutableAppContext) { + cx.add_global_action({ + let client = client.clone(); + move |_: &Authenticate, cx| { + let client = client.clone(); + cx.spawn( + |cx| async move { client.authenticate_and_connect(true, &cx).log_err().await }, + ) .detach(); + } + }); + cx.add_global_action({ + let client = client.clone(); + move |_: &TestTelemetry, _| { + client.log_event( + "test_telemetry", + json!({ + "test_property": "test_value" + }), + ) + } }); } diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index a96dd26c20..a78e691459 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -1,4 +1,4 @@ -use crate::{http::HttpClient, ZED_SECRET_CLIENT_TOKEN}; +use crate::{http::HttpClient, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; use gpui::{ executor::Background, serde_json::{self, value::Map, Value}, @@ -22,7 +22,6 @@ pub struct Telemetry { #[derive(Default)] struct TelemetryState { - metrics_id: Option, device_id: Option, app_version: Option, os_version: Option, @@ -33,7 +32,6 @@ struct TelemetryState { #[derive(Serialize)] struct RecordEventParams { token: &'static str, - metrics_id: Option, device_id: Option, app_version: Option, os_version: Option, @@ -48,8 +46,13 @@ struct Event { properties: Option>, } -const MAX_QUEUE_LEN: usize = 30; -const EVENTS_URI: &'static str = "https://zed.dev/api/telemetry"; +#[cfg(debug_assertions)] +const MAX_QUEUE_LEN: usize = 1; + +#[cfg(not(debug_assertions))] +const MAX_QUEUE_LEN: usize = 10; + +const EVENTS_URI: &'static str = "api/telemetry"; const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(30); impl Telemetry { @@ -61,7 +64,6 @@ impl Telemetry { state: Mutex::new(TelemetryState { os_version: platform.os_version().log_err(), app_version: platform.app_version().log_err(), - metrics_id: None, device_id: None, queue: Default::default(), flush_task: Default::default(), @@ -69,10 +71,6 @@ impl Telemetry { }) } - pub fn set_metrics_id(&self, metrics_id: Option) { - self.state.lock().metrics_id = metrics_id; - } - pub fn log_event(self: &Arc, kind: &str, properties: Value) { let mut state = self.state.lock(); state.queue.push(Event { @@ -88,6 +86,7 @@ impl Telemetry { }, }); if state.queue.len() >= MAX_QUEUE_LEN { + drop(state); self.flush(); } else { let this = self.clone(); @@ -105,7 +104,6 @@ impl Telemetry { let client = self.client.clone(); let app_version = state.app_version; let os_version = state.os_version; - let metrics_id = state.metrics_id; let device_id = state.device_id.clone(); state.flush_task.take(); self.executor @@ -115,11 +113,13 @@ impl Telemetry { events, app_version: app_version.map(|v| v.to_string()), os_version: os_version.map(|v| v.to_string()), - metrics_id, device_id, }) .log_err()?; - let request = Request::post(EVENTS_URI).body(body.into()).log_err()?; + let request = Request::post(format!("{}/{}", *ZED_SERVER_URL, EVENTS_URI)) + .header("Content-Type", "application/json") + .body(body.into()) + .log_err()?; client.send(request).await.log_err(); Some(()) }) diff --git a/crates/collab/migrations/20220913211150_create_signups.down.sql b/crates/collab/migrations/20220913211150_create_signups.down.sql index 59b20b1128..f67c10dd01 100644 --- a/crates/collab/migrations/20220913211150_create_signups.down.sql +++ b/crates/collab/migrations/20220913211150_create_signups.down.sql @@ -1,9 +1,7 @@ DROP TABLE signups; ALTER TABLE users - DROP COLUMN github_user_id, - DROP COLUMN metrics_id; - -DROP SEQUENCE metrics_id_seq; + DROP COLUMN github_user_id; DROP INDEX index_users_on_email_address; +DROP INDEX index_users_on_github_user_id; diff --git a/crates/collab/migrations/20220913211150_create_signups.up.sql b/crates/collab/migrations/20220913211150_create_signups.up.sql index 5f02bd6887..35e334ea5f 100644 --- a/crates/collab/migrations/20220913211150_create_signups.up.sql +++ b/crates/collab/migrations/20220913211150_create_signups.up.sql @@ -1,12 +1,10 @@ -CREATE SEQUENCE metrics_id_seq; - CREATE TABLE IF NOT EXISTS "signups" ( - "id" SERIAL PRIMARY KEY NOT NULL, + "id" SERIAL PRIMARY KEY, "email_address" VARCHAR NOT NULL, "email_confirmation_code" VARCHAR(64) NOT NULL, "email_confirmation_sent" BOOLEAN NOT NULL, - "metrics_id" INTEGER NOT NULL DEFAULT nextval('metrics_id_seq'), "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "device_id" VARCHAR NOT NULL, "user_id" INTEGER REFERENCES users (id) ON DELETE CASCADE, "inviting_user_id" INTEGER REFERENCES users (id) ON DELETE SET NULL, @@ -23,11 +21,7 @@ CREATE UNIQUE INDEX "index_signups_on_email_address" ON "signups" ("email_addres CREATE INDEX "index_signups_on_email_confirmation_sent" ON "signups" ("email_confirmation_sent"); ALTER TABLE "users" - ADD "github_user_id" INTEGER, - ADD "metrics_id" INTEGER DEFAULT nextval('metrics_id_seq'); + ADD "github_user_id" INTEGER; CREATE INDEX "index_users_on_email_address" ON "users" ("email_address"); CREATE INDEX "index_users_on_github_user_id" ON "users" ("github_user_id"); - -UPDATE users -SET metrics_id = nextval('metrics_id_seq'); diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index 51b43119dc..a82363a56b 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -127,44 +127,52 @@ struct CreateUserParams { invite_count: i32, } +#[derive(Serialize, Debug)] +struct CreateUserResponse { + user: User, + signup_device_id: Option, +} + async fn create_user( Json(params): Json, Extension(app): Extension>, Extension(rpc_server): Extension>, -) -> Result> { +) -> Result> { let user = NewUserParams { github_login: params.github_login, github_user_id: params.github_user_id, invite_count: params.invite_count, }; - let (user_id, inviter_id) = - // Creating a user via the normal signup process - if let Some(email_confirmation_code) = params.email_confirmation_code { - app.db - .create_user_from_invite( - &Invite { - email_address: params.email_address, - email_confirmation_code, - }, - user, - ) - .await? - } - // Creating a user as an admin - else { - ( - app.db - .create_user(¶ms.email_address, false, user) - .await?, - None, + 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 + .create_user_from_invite( + &Invite { + email_address: params.email_address, + email_confirmation_code, + }, + user, ) - }; - - if let Some(inviter_id) = inviter_id { - rpc_server - .invite_code_redeemed(inviter_id, user_id) - .await - .trace_err(); + .await?; + user_id = result.0; + signup_device_id = Some(result.2); + if let Some(inviter_id) = result.1 { + rpc_server + .invite_code_redeemed(inviter_id, user_id) + .await + .trace_err(); + } + } + // Creating a user as an admin + else { + user_id = app + .db + .create_user(¶ms.email_address, false, user) + .await?; + signup_device_id = None; } let user = app @@ -173,7 +181,10 @@ async fn create_user( .await? .ok_or_else(|| anyhow!("couldn't find the user we just created"))?; - Ok(Json(user)) + Ok(Json(CreateUserResponse { + user, + signup_device_id, + })) } #[derive(Deserialize)] @@ -396,17 +407,12 @@ async fn get_user_for_invite_code( Ok(Json(app.db.get_user_for_invite_code(&code).await?)) } -#[derive(Serialize)] -struct CreateSignupResponse { - metrics_id: i32, -} - async fn create_signup( Json(params): Json, Extension(app): Extension>, -) -> Result> { - let metrics_id = app.db.create_signup(params).await?; - Ok(Json(CreateSignupResponse { metrics_id })) +) -> Result<()> { + app.db.create_signup(params).await?; + Ok(()) } async fn get_waitlist_summary( diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 81c87f213a..1518ec179f 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -37,7 +37,7 @@ pub trait Db: Send + Sync { async fn get_user_for_invite_code(&self, code: &str) -> Result; async fn create_invite_from_code(&self, code: &str, email_address: &str) -> Result; - async fn create_signup(&self, signup: Signup) -> Result; + async fn create_signup(&self, signup: Signup) -> Result<()>; async fn get_waitlist_summary(&self) -> Result; async fn get_unsent_invites(&self, count: usize) -> Result>; async fn record_sent_invites(&self, invites: &[Invite]) -> Result<()>; @@ -45,7 +45,7 @@ pub trait Db: Send + Sync { &self, invite: &Invite, user: NewUserParams, - ) -> Result<(UserId, Option)>; + ) -> Result<(UserId, Option, String)>; /// Registers a new project for the given user. async fn register_project(&self, host_user_id: UserId) -> Result; @@ -364,8 +364,8 @@ impl Db for PostgresDb { // signups - async fn create_signup(&self, signup: Signup) -> Result { - Ok(sqlx::query_scalar( + async fn create_signup(&self, signup: Signup) -> Result<()> { + sqlx::query( " INSERT INTO signups ( @@ -377,10 +377,11 @@ impl Db for PostgresDb { platform_windows, platform_unknown, editor_features, - programming_languages + programming_languages, + device_id ) VALUES - ($1, $2, 'f', $3, $4, $5, 'f', $6, $7) + ($1, $2, 'f', $3, $4, $5, 'f', $6, $7, $8) RETURNING id ", ) @@ -391,8 +392,10 @@ impl Db for PostgresDb { .bind(&signup.platform_windows) .bind(&signup.editor_features) .bind(&signup.programming_languages) - .fetch_one(&self.pool) - .await?) + .bind(&signup.device_id) + .execute(&self.pool) + .await?; + Ok(()) } async fn get_waitlist_summary(&self) -> Result { @@ -455,17 +458,17 @@ impl Db for PostgresDb { &self, invite: &Invite, user: NewUserParams, - ) -> Result<(UserId, Option)> { + ) -> Result<(UserId, Option, String)> { let mut tx = self.pool.begin().await?; - let (signup_id, metrics_id, existing_user_id, inviting_user_id): ( - i32, + let (signup_id, existing_user_id, inviting_user_id, device_id): ( i32, Option, Option, + String, ) = sqlx::query_as( " - SELECT id, metrics_id, user_id, inviting_user_id + SELECT id, user_id, inviting_user_id, device_id FROM signups WHERE email_address = $1 AND @@ -488,9 +491,9 @@ impl Db for PostgresDb { let user_id: UserId = sqlx::query_scalar( " INSERT INTO users - (email_address, github_login, github_user_id, admin, invite_count, invite_code, metrics_id) + (email_address, github_login, github_user_id, admin, invite_count, invite_code) VALUES - ($1, $2, $3, 'f', $4, $5, $6) + ($1, $2, $3, 'f', $4, $5) RETURNING id ", ) @@ -499,7 +502,6 @@ impl Db for PostgresDb { .bind(&user.github_user_id) .bind(&user.invite_count) .bind(random_invite_code()) - .bind(metrics_id) .fetch_one(&mut tx) .await?; @@ -550,7 +552,7 @@ impl Db for PostgresDb { } tx.commit().await?; - Ok((user_id, inviting_user_id)) + Ok((user_id, inviting_user_id, device_id)) } // invite codes @@ -1567,7 +1569,6 @@ pub struct User { pub id: UserId, pub github_login: String, pub github_user_id: Option, - pub metrics_id: i32, pub email_address: Option, pub admin: bool, pub invite_code: Option, @@ -1674,6 +1675,7 @@ pub struct Signup { pub platform_linux: bool, pub editor_features: Vec, pub programming_languages: Vec, + pub device_id: String, } #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromRow)] @@ -1802,7 +1804,6 @@ mod test { github_login: params.github_login, github_user_id: Some(params.github_user_id), email_address: Some(email_address.to_string()), - metrics_id: id + 100, admin, invite_code: None, invite_count: 0, @@ -1884,7 +1885,7 @@ mod test { // signups - async fn create_signup(&self, _signup: Signup) -> Result { + async fn create_signup(&self, _signup: Signup) -> Result<()> { unimplemented!() } @@ -1904,7 +1905,7 @@ mod test { &self, _invite: &Invite, _user: NewUserParams, - ) -> Result<(UserId, Option)> { + ) -> Result<(UserId, Option, String)> { unimplemented!() } diff --git a/crates/collab/src/db_tests.rs b/crates/collab/src/db_tests.rs index 64a3626a22..44697a59bd 100644 --- a/crates/collab/src/db_tests.rs +++ b/crates/collab/src/db_tests.rs @@ -957,7 +957,7 @@ async fn test_invite_codes() { .create_invite_from_code(&invite_code, "u2@example.com") .await .unwrap(); - let (user2, inviter) = db + let (user2, inviter, _) = db .create_user_from_invite( &user2_invite, NewUserParams { @@ -1007,7 +1007,7 @@ async fn test_invite_codes() { .create_invite_from_code(&invite_code, "u3@example.com") .await .unwrap(); - let (user3, inviter) = db + let (user3, inviter, _) = db .create_user_from_invite( &user3_invite, NewUserParams { @@ -1072,7 +1072,7 @@ async fn test_invite_codes() { .create_invite_from_code(&invite_code, "u4@example.com") .await .unwrap(); - let (user4, _) = db + let (user4, _, _) = db .create_user_from_invite( &user4_invite, NewUserParams { @@ -1139,20 +1139,18 @@ async fn test_signups() { let db = postgres.db(); // people sign up on the waitlist - let mut signup_metric_ids = Vec::new(); for i in 0..8 { - signup_metric_ids.push( - db.create_signup(Signup { - email_address: format!("person-{i}@example.com"), - platform_mac: true, - platform_linux: i % 2 == 0, - platform_windows: i % 4 == 0, - editor_features: vec!["speed".into()], - programming_languages: vec!["rust".into(), "c".into()], - }) - .await - .unwrap(), - ); + db.create_signup(Signup { + email_address: format!("person-{i}@example.com"), + platform_mac: true, + platform_linux: i % 2 == 0, + platform_windows: i % 4 == 0, + editor_features: vec!["speed".into()], + programming_languages: vec!["rust".into(), "c".into()], + device_id: format!("device_id_{i}"), + }) + .await + .unwrap(); } assert_eq!( @@ -1219,7 +1217,7 @@ async fn test_signups() { // user completes the signup process by providing their // github account. - let (user_id, inviter_id) = db + let (user_id, inviter_id, signup_device_id) = db .create_user_from_invite( &Invite { email_address: signups_batch1[0].email_address.clone(), @@ -1238,7 +1236,7 @@ async fn test_signups() { assert_eq!(user.github_login, "person-0"); assert_eq!(user.email_address.as_deref(), Some("person-0@example.com")); assert_eq!(user.invite_count, 5); - assert_eq!(user.metrics_id, signup_metric_ids[0]); + assert_eq!(signup_device_id, "device_id_0"); // cannot redeem the same signup again. db.create_user_from_invite(