diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index e2c8570c567675fa311cc159e949d99f980b09a3..13b78e7e7da731d9f7312b5e2d8ccb5572e01fef 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -46,31 +46,13 @@ struct TelemetryState { first_event_date_time: Option, event_coalescer: EventCoalescer, max_queue_size: usize, - project_marker_patterns: ProjectMarkerPatterns, + worktrees_with_project_type_events_sent: HashSet, os_name: String, app_version: String, os_version: Option, } -#[derive(Debug)] -struct ProjectMarkerPatterns(Vec<(Regex, ProjectCache)>); - -#[derive(Debug)] -struct ProjectCache { - name: String, - worktree_ids_reported: HashSet, -} - -impl ProjectCache { - fn new(name: String) -> Self { - Self { - name, - worktree_ids_reported: HashSet::default(), - } - } -} - #[cfg(debug_assertions)] const MAX_QUEUE_LEN: usize = 5; @@ -92,6 +74,10 @@ static ZED_CLIENT_CHECKSUM_SEED: LazyLock>> = LazyLock::new(|| { }) }); +static DOTNET_PROJECT_FILES_REGEX: LazyLock = LazyLock::new(|| { + Regex::new(r"^(global\.json|Directory\.Build\.props|.*\.(csproj|fsproj|vbproj|sln))$").unwrap() +}); + pub fn os_name() -> String { #[cfg(target_os = "macos")] { @@ -195,27 +181,7 @@ impl Telemetry { first_event_date_time: None, event_coalescer: EventCoalescer::new(clock.clone()), max_queue_size: MAX_QUEUE_LEN, - project_marker_patterns: ProjectMarkerPatterns(vec![ - ( - Regex::new(r"^pnpm-lock\.yaml$").unwrap(), - ProjectCache::new("pnpm".to_string()), - ), - ( - Regex::new(r"^yarn\.lock$").unwrap(), - ProjectCache::new("yarn".to_string()), - ), - ( - Regex::new(r"^package\.json$").unwrap(), - ProjectCache::new("node".to_string()), - ), - ( - Regex::new( - r"^(global\.json|Directory\.Build\.props|.*\.(csproj|fsproj|vbproj|sln))$", - ) - .unwrap(), - ProjectCache::new("dotnet".to_string()), - ), - ]), + worktrees_with_project_type_events_sent: HashSet::new(), os_version: None, os_name: os_name(), @@ -379,7 +345,7 @@ impl Telemetry { } } - pub fn report_discovered_project_events( + pub fn report_discovered_project_type_events( self: &Arc, worktree_id: WorktreeId, updated_entries_set: &UpdatedEntriesSet, @@ -397,32 +363,40 @@ impl Telemetry { updated_entries_set: &UpdatedEntriesSet, ) -> Vec { let mut state = self.state.lock(); - state - .project_marker_patterns - .0 - .iter_mut() - .filter_map(|(pattern, project_cache)| { - if project_cache.worktree_ids_reported.contains(&worktree_id) { - return None; - } + let mut project_names: HashSet = HashSet::new(); - let project_file_found = updated_entries_set.iter().any(|(path, _, _)| { - path.as_ref() - .file_name() - .and_then(|name| name.to_str()) - .map(|name_str| pattern.is_match(name_str)) - .unwrap_or(false) - }); + if state + .worktrees_with_project_type_events_sent + .contains(&worktree_id) + { + return project_names.into_iter().collect(); + } - if !project_file_found { - return None; - } + for (path, _, _) in updated_entries_set.iter() { + let Some(file_name) = path.file_name().and_then(|f| f.to_str()) else { + continue; + }; - project_cache.worktree_ids_reported.insert(worktree_id); + if file_name == "pnpm-lock.yaml" { + project_names.insert("pnpm".to_string()); + } else if file_name == "yarn.lock" { + project_names.insert("yarn".to_string()); + } else if file_name == "package.json" { + project_names.insert("node".to_string()); + } else if DOTNET_PROJECT_FILES_REGEX.is_match(file_name) { + project_names.insert("dotnet".to_string()); + } + } - Some(project_cache.name.clone()) - }) - .collect() + if !project_names.is_empty() { + state + .worktrees_with_project_type_events_sent + .insert(worktree_id); + } + + let mut project_names_vec: Vec = project_names.into_iter().collect(); + project_names_vec.sort(); + project_names_vec } fn report_event(self: &Arc, event: Event) { @@ -786,7 +760,16 @@ mod tests { test_project_discovery_helper(telemetry.clone(), vec!["file.csproj"], vec!["dotnet"], 3); test_project_discovery_helper(telemetry.clone(), vec!["file.fsproj"], vec!["dotnet"], 4); test_project_discovery_helper(telemetry.clone(), vec!["file.vbproj"], vec!["dotnet"], 5); - test_project_discovery_helper(telemetry, vec!["file.sln"], vec!["dotnet"], 6); + test_project_discovery_helper(telemetry.clone(), vec!["file.sln"], vec!["dotnet"], 6); + + // Each worktree should only send a single project type event, even when + // encountering multiple files associated with that project type + test_project_discovery_helper( + telemetry, + vec!["global.json", "Directory.Build.props"], + vec!["dotnet"], + 7, + ); } // TODO: diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f8db5362ff4f41b642f9736be768fbd6bb22516f..fc345bb315dd25dcf87ebe0f4d17257566231478 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2945,7 +2945,7 @@ impl Project { WorktreeStoreEvent::WorktreeUpdatedEntries(worktree_id, changes) => { self.client() .telemetry() - .report_discovered_project_events(*worktree_id, changes); + .report_discovered_project_type_events(*worktree_id, changes); cx.emit(Event::WorktreeUpdatedEntries(*worktree_id, changes.clone())) } WorktreeStoreEvent::WorktreeDeletedEntry(worktree_id, id) => {