diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 866d0acc0e..a951652932 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,7 +56,7 @@ jobs: MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }} APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} - ZED_AMPLITUDE_API_KEY: ${{ secrets.ZED_AMPLITUDE_API_KEY }} + ZED_MIXPANEL_TOKEN: ${{ secrets.ZED_MIXPANEL_TOKEN }} steps: - name: Install Rust run: | diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index 9a3b2376df..cf8293b963 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -20,14 +20,3 @@ jobs: ${{ github.event.release.body }} ``` - amplitude_release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: "3.10.5" - architecture: "x64" - cache: "pip" - - run: pip install -r script/amplitude_release/requirements.txt - - run: python script/amplitude_release/main.py ${{ github.event.release.tag_name }} ${{ secrets.ZED_AMPLITUDE_API_KEY }} ${{ secrets.ZED_AMPLITUDE_SECRET_KEY }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2d721f8ad2..40a18ccc1d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,3 @@ /assets/themes/*.json /assets/themes/internal/*.json /assets/themes/experiments/*.json -**/venv \ No newline at end of file diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 6829eab531..3fafce09c0 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -24,7 +24,6 @@ use uuid::Uuid; pub struct Telemetry { http_client: Arc, executor: Arc, - session_id: u128, state: Mutex, } @@ -35,43 +34,54 @@ struct TelemetryState { app_version: Option>, os_version: Option>, os_name: &'static str, - queue: Vec, + queue: Vec, next_event_id: usize, flush_task: Option>, log_file: Option, } -const AMPLITUDE_EVENTS_URL: &'static str = "https://api2.amplitude.com/batch"; +const MIXPANEL_EVENTS_URL: &'static str = "https://api.mixpanel.com/track"; +const MIXPANEL_ENGAGE_URL: &'static str = "https://api.mixpanel.com/engage#profile-set"; lazy_static! { - static ref AMPLITUDE_API_KEY: Option = std::env::var("ZED_AMPLITUDE_API_KEY") + static ref MIXPANEL_TOKEN: Option = std::env::var("ZED_MIXPANEL_TOKEN") .ok() - .or_else(|| option_env!("ZED_AMPLITUDE_API_KEY").map(|key| key.to_string())); + .or_else(|| option_env!("ZED_MIXPANEL_TOKEN").map(|key| key.to_string())); } -#[derive(Serialize)] -struct AmplitudeEventBatch { - api_key: &'static str, - events: Vec, +#[derive(Serialize, Debug)] +struct MixpanelEvent { + event: String, + properties: MixpanelEventProperties, } -#[derive(Serialize)] -struct AmplitudeEvent { - #[serde(skip_serializing_if = "Option::is_none")] - user_id: Option>, - device_id: Option>, - event_type: String, - #[serde(skip_serializing_if = "Option::is_none")] +#[derive(Serialize, Debug)] +struct MixpanelEventProperties { + // Mixpanel required fields + #[serde(skip_serializing_if = "str::is_empty")] + token: &'static str, + time: u128, + distinct_id: Option>, + #[serde(rename = "$insert_id")] + insert_id: usize, + // Custom fields + #[serde(skip_serializing_if = "Option::is_none", flatten)] event_properties: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - user_properties: Option>, os_name: &'static str, os_version: Option>, app_version: Option>, + signed_in: bool, platform: &'static str, - event_id: usize, - session_id: u128, - time: u128, +} + +#[derive(Serialize)] +struct MixpanelEngageRequest { + #[serde(rename = "$token")] + token: &'static str, + #[serde(rename = "$distinct_id")] + distinct_id: Arc, + #[serde(rename = "$set")] + set: Value, } #[cfg(debug_assertions)] @@ -92,10 +102,6 @@ impl Telemetry { let this = Arc::new(Self { http_client: client, executor: cx.background().clone(), - session_id: SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis(), state: Mutex::new(TelemetryState { os_version: platform .os_version() @@ -107,15 +113,15 @@ impl Telemetry { .log_err() .map(|v| v.to_string().into()), device_id: None, + metrics_id: None, queue: Default::default(), flush_task: Default::default(), next_event_id: 0, log_file: None, - metrics_id: None, }), }); - if AMPLITUDE_API_KEY.is_some() { + if MIXPANEL_TOKEN.is_some() { this.executor .spawn({ let this = this.clone(); @@ -148,11 +154,14 @@ impl Telemetry { device_id }; - let device_id = Some(Arc::from(device_id)); + let device_id: Arc = device_id.into(); let mut state = this.state.lock(); - state.device_id = device_id.clone(); + state.device_id = Some(device_id.clone()); for event in &mut state.queue { - event.device_id = device_id.clone(); + event + .properties + .distinct_id + .get_or_insert_with(|| device_id.clone()); } if !state.queue.is_empty() { drop(state); @@ -171,56 +180,63 @@ impl Telemetry { metrics_id: Option, is_staff: bool, ) { - let is_signed_in = metrics_id.is_some(); - self.state.lock().metrics_id = metrics_id.map(|s| s.into()); - if is_signed_in { - self.report_event_with_user_properties( - "$identify", - Default::default(), - json!({ "$set": { "staff": is_staff } }), - ) + let this = self.clone(); + let mut state = self.state.lock(); + let device_id = state.device_id.clone(); + let metrics_id: Option> = metrics_id.map(|id| id.into()); + state.metrics_id = metrics_id.clone(); + drop(state); + + if let Some((token, device_id)) = MIXPANEL_TOKEN.as_ref().zip(device_id) { + self.executor + .spawn( + async move { + let json_bytes = serde_json::to_vec(&[MixpanelEngageRequest { + token, + distinct_id: device_id, + set: json!({ "staff": is_staff, "id": metrics_id }), + }])?; + + eprintln!("request: {}", std::str::from_utf8(&json_bytes).unwrap()); + + let request = Request::post(MIXPANEL_ENGAGE_URL) + .header("Content-Type", "application/json") + .body(json_bytes.into())?; + let response = this.http_client.send(request).await?; + + eprintln!("response: {:?} {:?}", response.status(), response.body()); + + Ok(()) + } + .log_err(), + ) + .detach(); } } pub fn report_event(self: &Arc, kind: &str, properties: Value) { - self.report_event_with_user_properties(kind, properties, Default::default()); - } - - fn report_event_with_user_properties( - self: &Arc, - kind: &str, - properties: Value, - user_properties: Value, - ) { - if AMPLITUDE_API_KEY.is_none() { - return; - } - let mut state = self.state.lock(); - let event = AmplitudeEvent { - event_type: kind.to_string(), - time: SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis(), - session_id: self.session_id, - event_properties: if let Value::Object(properties) = properties { - Some(properties) - } else { - None + let event = MixpanelEvent { + event: kind.to_string(), + properties: MixpanelEventProperties { + token: "", + time: SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(), + distinct_id: state.device_id.clone(), + insert_id: post_inc(&mut state.next_event_id), + event_properties: if let Value::Object(properties) = properties { + Some(properties) + } else { + None + }, + os_name: state.os_name, + os_version: state.os_version.clone(), + app_version: state.app_version.clone(), + signed_in: state.metrics_id.is_some(), + platform: "Zed", }, - user_properties: if let Value::Object(user_properties) = user_properties { - Some(user_properties) - } else { - None - }, - user_id: state.metrics_id.clone(), - device_id: state.device_id.clone(), - os_name: state.os_name, - platform: "Zed", - os_version: state.os_version.clone(), - app_version: state.app_version.clone(), - event_id: post_inc(&mut state.next_event_id), }; state.queue.push(event); if state.device_id.is_some() { @@ -240,11 +256,11 @@ impl Telemetry { fn flush(self: &Arc) { let mut state = self.state.lock(); - let events = mem::take(&mut state.queue); + let mut events = mem::take(&mut state.queue); state.flush_task.take(); drop(state); - if let Some(api_key) = AMPLITUDE_API_KEY.as_ref() { + if let Some(token) = MIXPANEL_TOKEN.as_ref() { let this = self.clone(); self.executor .spawn( @@ -253,20 +269,28 @@ impl Telemetry { if let Some(file) = &mut this.state.lock().log_file { let file = file.as_file_mut(); - for event in &events { + for event in &mut events { json_bytes.clear(); serde_json::to_writer(&mut json_bytes, event)?; file.write_all(&json_bytes)?; file.write(b"\n")?; + + event.properties.token = token; } } - let batch = AmplitudeEventBatch { api_key, events }; json_bytes.clear(); - serde_json::to_writer(&mut json_bytes, &batch)?; - let request = - Request::post(AMPLITUDE_EVENTS_URL).body(json_bytes.into())?; - this.http_client.send(request).await?; + serde_json::to_writer(&mut json_bytes, &events)?; + + eprintln!("request: {}", std::str::from_utf8(&json_bytes).unwrap()); + + let request = Request::post(MIXPANEL_EVENTS_URL) + .header("Content-Type", "application/json") + .body(json_bytes.into())?; + let response = this.http_client.send(request).await?; + + eprintln!("response: {:?} {:?}", response.status(), response.body()); + Ok(()) } .log_err(), diff --git a/crates/zed/build.rs b/crates/zed/build.rs index d3167851a0..4a5643fd8e 100644 --- a/crates/zed/build.rs +++ b/crates/zed/build.rs @@ -3,8 +3,8 @@ use std::process::Command; fn main() { println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.14"); - if let Ok(api_key) = std::env::var("ZED_AMPLITUDE_API_KEY") { - println!("cargo:rustc-env=ZED_AMPLITUDE_API_KEY={api_key}"); + if let Ok(api_key) = std::env::var("ZED_MIXPANEL_TOKEN") { + println!("cargo:rustc-env=ZED_MIXPANEL_TOKEN={api_key}"); } let output = Command::new("npm") diff --git a/script/amplitude_release/main.py b/script/amplitude_release/main.py deleted file mode 100644 index 160e40b66c..0000000000 --- a/script/amplitude_release/main.py +++ /dev/null @@ -1,30 +0,0 @@ -import datetime -import sys - -from amplitude_python_sdk.v2.clients.releases_client import ReleasesAPIClient -from amplitude_python_sdk.v2.models.releases import Release - - -def main(): - version = sys.argv[1] - version = version.removeprefix("v") - - api_key = sys.argv[2] - secret_key = sys.argv[3] - - current_datetime = datetime.datetime.now(datetime.timezone.utc) - current_datetime = current_datetime.strftime("%Y-%m-%d %H:%M:%S") - - release = Release( - title=version, - version=version, - release_start=current_datetime, - created_by="GitHub Release Workflow", - chart_visibility=True - ) - - ReleasesAPIClient(api_key=api_key, secret_key=secret_key).create(release) - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/script/amplitude_release/requirements.txt b/script/amplitude_release/requirements.txt deleted file mode 100644 index 7ed3ea6515..0000000000 --- a/script/amplitude_release/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -amplitude-python-sdk==0.2.0 \ No newline at end of file