Add settings events (#3847)

Joseph T. Lyons created

Adds the infractucture for settings events and specifically tracks theme
settings. Currently, we only take note of the theme at app open and when
the user switches the theme with the theme selector. Changes at the
config file are ignored, as putting code that low leads to a lot of
chances of reporting theme events when the user hasn't done anything.
This change is done in both Zed 1 and Zed 2.

I'll open up a PR on zed.dev and adjust the database accordingly.

Release Notes:

- N/A

Change summary

Cargo.lock                                   |  2 ++
crates/client/src/telemetry.rs               | 20 ++++++++++++++++++++
crates/client2/src/telemetry.rs              | 20 ++++++++++++++++++++
crates/theme_selector/Cargo.toml             |  1 +
crates/theme_selector/src/theme_selector.rs  | 17 +++++++++++++++--
crates/theme_selector2/Cargo.toml            | 11 ++++++-----
crates/theme_selector2/src/theme_selector.rs | 14 ++++++++++++--
crates/zed/src/main.rs                       |  5 +++++
crates/zed2/src/main.rs                      |  5 +++++
9 files changed, 86 insertions(+), 9 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -9856,6 +9856,7 @@ dependencies = [
 name = "theme_selector"
 version = "0.1.0"
 dependencies = [
+ "client",
  "editor",
  "feature_flags",
  "fs",
@@ -9876,6 +9877,7 @@ dependencies = [
 name = "theme_selector2"
 version = "0.1.0"
 dependencies = [
+ "client2",
  "editor2",
  "feature_flags2",
  "fs2",

crates/client/src/telemetry.rs 🔗

@@ -113,6 +113,11 @@ pub enum ClickhouseEvent {
         operation: &'static str,
         milliseconds_since_first_event: i64,
     },
+    Setting {
+        setting: &'static str,
+        value: String,
+        milliseconds_since_first_event: i64,
+    },
 }
 
 #[cfg(debug_assertions)]
@@ -353,6 +358,21 @@ impl Telemetry {
         self.report_clickhouse_event(event, telemetry_settings, true)
     }
 
+    pub fn report_setting_event(
+        self: &Arc<Self>,
+        telemetry_settings: TelemetrySettings,
+        setting: &'static str,
+        value: String,
+    ) {
+        let event = ClickhouseEvent::Setting {
+            setting,
+            value,
+            milliseconds_since_first_event: self.milliseconds_since_first_event(),
+        };
+
+        self.report_clickhouse_event(event, telemetry_settings, false)
+    }
+
     fn milliseconds_since_first_event(&self) -> i64 {
         let mut state = self.state.lock();
         match state.first_event_datetime {

crates/client2/src/telemetry.rs 🔗

@@ -112,6 +112,11 @@ pub enum ClickhouseEvent {
         operation: &'static str,
         milliseconds_since_first_event: i64,
     },
+    Setting {
+        setting: &'static str,
+        value: String,
+        milliseconds_since_first_event: i64,
+    },
 }
 
 #[cfg(debug_assertions)]
@@ -377,6 +382,21 @@ impl Telemetry {
         self.report_clickhouse_event(event, telemetry_settings, true)
     }
 
+    pub fn report_setting_event(
+        self: &Arc<Self>,
+        telemetry_settings: TelemetrySettings,
+        setting: &'static str,
+        value: String,
+    ) {
+        let event = ClickhouseEvent::Setting {
+            setting,
+            value,
+            milliseconds_since_first_event: self.milliseconds_since_first_event(),
+        };
+
+        self.report_clickhouse_event(event, telemetry_settings, false)
+    }
+
     fn milliseconds_since_first_event(&self) -> i64 {
         let mut state = self.state.lock();
         match state.first_event_datetime {

crates/theme_selector/Cargo.toml 🔗

@@ -9,6 +9,7 @@ path = "src/theme_selector.rs"
 doctest = false
 
 [dependencies]
+client = { path = "../client" }
 editor = { path = "../editor" }
 fuzzy = { path = "../fuzzy" }
 fs = { path = "../fs" }

crates/theme_selector/src/theme_selector.rs 🔗

@@ -1,3 +1,4 @@
+use client::{telemetry::Telemetry, TelemetrySettings};
 use feature_flags::FeatureFlagAppExt;
 use fs::Fs;
 use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
@@ -19,7 +20,8 @@ pub fn init(cx: &mut AppContext) {
 pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
     workspace.toggle_modal(cx, |workspace, cx| {
         let fs = workspace.app_state().fs.clone();
-        cx.add_view(|cx| ThemeSelector::new(ThemeSelectorDelegate::new(fs, cx), cx))
+        let telemetry = workspace.client().telemetry().clone();
+        cx.add_view(|cx| ThemeSelector::new(ThemeSelectorDelegate::new(fs, telemetry, cx), cx))
     });
 }
 
@@ -48,10 +50,15 @@ pub struct ThemeSelectorDelegate {
     original_theme: Arc<Theme>,
     selection_completed: bool,
     selected_index: usize,
+    telemetry: Arc<Telemetry>,
 }
 
 impl ThemeSelectorDelegate {
-    fn new(fs: Arc<dyn Fs>, cx: &mut ViewContext<ThemeSelector>) -> Self {
+    fn new(
+        fs: Arc<dyn Fs>,
+        telemetry: Arc<Telemetry>,
+        cx: &mut ViewContext<ThemeSelector>,
+    ) -> Self {
         let original_theme = theme::current(cx).clone();
 
         let staff_mode = cx.is_staff();
@@ -74,6 +81,7 @@ impl ThemeSelectorDelegate {
             original_theme: original_theme.clone(),
             selected_index: 0,
             selection_completed: false,
+            telemetry,
         };
         this.select_if_matching(&original_theme.meta.name);
         this
@@ -124,6 +132,11 @@ impl PickerDelegate for ThemeSelectorDelegate {
         self.selection_completed = true;
 
         let theme_name = theme::current(cx).meta.name.clone();
+
+        let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
+        self.telemetry
+            .report_setting_event(telemetry_settings, "theme", theme_name.to_string());
+
         update_settings_file::<ThemeSettings>(self.fs.clone(), cx, |settings| {
             settings.theme = Some(theme_name);
         });

crates/theme_selector2/Cargo.toml 🔗

@@ -9,17 +9,18 @@ path = "src/theme_selector.rs"
 doctest = false
 
 [dependencies]
+client = { package = "client2", path = "../client2" }
 editor = { package = "editor2", path = "../editor2" }
-fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
+feature_flags = { package = "feature_flags2", path = "../feature_flags2" }
 fs = { package = "fs2", path = "../fs2" }
+fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
 gpui = { package = "gpui2", path = "../gpui2" }
-ui = { package = "ui2", path = "../ui2" }
 picker = { package = "picker2", path = "../picker2" }
-theme = { package = "theme2", path = "../theme2" }
 settings = { package = "settings2", path = "../settings2" }
-feature_flags = { package = "feature_flags2", path = "../feature_flags2" }
-workspace = { package = "workspace2", path = "../workspace2" }
+theme = { package = "theme2", path = "../theme2" }
+ui = { package = "ui2", path = "../ui2" }
 util = { path = "../util" }
+workspace = { package = "workspace2", path = "../workspace2" }
 log.workspace = true
 parking_lot.workspace = true
 postage.workspace = true

crates/theme_selector2/src/theme_selector.rs 🔗

@@ -1,3 +1,4 @@
+use client::{telemetry::Telemetry, TelemetrySettings};
 use feature_flags::FeatureFlagAppExt;
 use fs::Fs;
 use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
@@ -6,7 +7,7 @@ use gpui::{
     VisualContext, WeakView,
 };
 use picker::{Picker, PickerDelegate};
-use settings::{update_settings_file, SettingsStore};
+use settings::{update_settings_file, Settings, SettingsStore};
 use std::sync::Arc;
 use theme::{Theme, ThemeMeta, ThemeRegistry, ThemeSettings};
 use ui::{prelude::*, v_stack, ListItem};
@@ -26,9 +27,10 @@ pub fn init(cx: &mut AppContext) {
 
 pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
     let fs = workspace.app_state().fs.clone();
+    let telemetry = workspace.client().telemetry().clone();
     workspace.toggle_modal(cx, |cx| {
         ThemeSelector::new(
-            ThemeSelectorDelegate::new(cx.view().downgrade(), fs, cx),
+            ThemeSelectorDelegate::new(cx.view().downgrade(), fs, telemetry, cx),
             cx,
         )
     });
@@ -88,6 +90,7 @@ pub struct ThemeSelectorDelegate {
     original_theme: Arc<Theme>,
     selection_completed: bool,
     selected_index: usize,
+    telemetry: Arc<Telemetry>,
     view: WeakView<ThemeSelector>,
 }
 
@@ -95,6 +98,7 @@ impl ThemeSelectorDelegate {
     fn new(
         weak_view: WeakView<ThemeSelector>,
         fs: Arc<dyn Fs>,
+        telemetry: Arc<Telemetry>,
         cx: &mut ViewContext<ThemeSelector>,
     ) -> Self {
         let original_theme = cx.theme().clone();
@@ -124,6 +128,7 @@ impl ThemeSelectorDelegate {
             original_theme: original_theme.clone(),
             selected_index: 0,
             selection_completed: false,
+            telemetry,
             view: weak_view,
         };
         this.select_if_matching(&original_theme.name);
@@ -177,6 +182,11 @@ impl PickerDelegate for ThemeSelectorDelegate {
         self.selection_completed = true;
 
         let theme_name = cx.theme().name.clone();
+
+        let telemetry_settings = TelemetrySettings::get_global(cx).clone();
+        self.telemetry
+            .report_setting_event(telemetry_settings, "theme", theme_name.to_string());
+
         update_settings_file::<ThemeSettings>(self.fs.clone(), cx, move |settings| {
             settings.theme = Some(theme_name.to_string());
         });

crates/zed/src/main.rs 🔗

@@ -168,6 +168,11 @@ fn main() {
 
         client.telemetry().start(installation_id, session_id, cx);
         let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
+        client.telemetry().report_setting_event(
+            telemetry_settings,
+            "theme",
+            theme::current(cx).meta.name.to_string(),
+        );
         let event_operation = match existing_installation_id_found {
             Some(false) => "first open",
             _ => "open",

crates/zed2/src/main.rs 🔗

@@ -177,6 +177,11 @@ fn main() {
 
         client.telemetry().start(installation_id, session_id, cx);
         let telemetry_settings = *client::TelemetrySettings::get_global(cx);
+        client.telemetry().report_setting_event(
+            telemetry_settings,
+            "theme",
+            cx.theme().name.to_string(),
+        );
         let event_operation = match existing_installation_id_found {
             Some(false) => "first open",
             _ => "open",