diff --git a/rust/telemetry/src/analytics.rs b/rust/telemetry/src/analytics.rs index b159f08e9..3c21e3c5b 100644 --- a/rust/telemetry/src/analytics.rs +++ b/rust/telemetry/src/analytics.rs @@ -6,7 +6,13 @@ use sha2::Digest as _; use crate::{Env, posthog::RUNTIME}; -pub fn new_session(distinct_id: String, api_url: String) { +pub fn new_session(maybe_legacy_id: String, api_url: String) { + let distinct_id = if uuid::Uuid::from_str(&maybe_legacy_id).is_ok() { + hex::encode(sha2::Sha256::digest(&maybe_legacy_id)) + } else { + maybe_legacy_id + }; + RUNTIME.spawn(async move { if let Err(e) = capture( "new_session", @@ -23,11 +29,19 @@ pub fn new_session(distinct_id: String, api_url: String) { /// Associate several properties with a particular "distinct_id" in PostHog. pub fn identify( - distinct_id: String, + maybe_legacy_id: String, api_url: String, release: String, account_slug: Option, ) { + let is_legacy_id = uuid::Uuid::from_str(&maybe_legacy_id).is_ok(); + + let distinct_id = if is_legacy_id { + hex::encode(sha2::Sha256::digest(&maybe_legacy_id)) + } else { + maybe_legacy_id.clone() + }; + RUNTIME.spawn({ let distinct_id = distinct_id.clone(); let api_url = api_url.clone(); @@ -53,14 +67,14 @@ pub fn identify( }); // Create an alias ID for the user so we can also find them under the "external ID" used in the portal. - if uuid::Uuid::from_str(&distinct_id).is_ok() { + if is_legacy_id { RUNTIME.spawn(async move { if let Err(e) = capture( "$create_alias", distinct_id.clone(), api_url, CreateAliasProperties { - alias: hex::encode(sha2::Sha256::digest(&distinct_id)), + alias: maybe_legacy_id, distinct_id, }, ) diff --git a/rust/telemetry/src/feature_flags.rs b/rust/telemetry/src/feature_flags.rs index 6bea0dcf5..1efb75d7f 100644 --- a/rust/telemetry/src/feature_flags.rs +++ b/rust/telemetry/src/feature_flags.rs @@ -1,4 +1,5 @@ use std::{ + str::FromStr as _, sync::{ LazyLock, atomic::{AtomicBool, Ordering}, @@ -8,6 +9,7 @@ use std::{ use anyhow::{Context as _, Result, bail}; use serde::{Deserialize, Serialize}; +use sha2::Digest as _; use crate::{ Env, @@ -88,7 +90,13 @@ pub(crate) async fn reeval_timer() { } } -async fn decide(distinct_id: String, api_key: String) -> Result { +async fn decide(maybe_legacy_id: String, api_key: String) -> Result { + let distinct_id = if uuid::Uuid::from_str(&maybe_legacy_id).is_ok() { + hex::encode(sha2::Sha256::digest(&maybe_legacy_id)) + } else { + maybe_legacy_id + }; + let response = reqwest::ClientBuilder::new() .connection_verbose(true) .build()? diff --git a/rust/telemetry/src/lib.rs b/rust/telemetry/src/lib.rs index 989ac05b1..4907a6e2f 100644 --- a/rust/telemetry/src/lib.rs +++ b/rust/telemetry/src/lib.rs @@ -1,6 +1,6 @@ #![cfg_attr(test, allow(clippy::unwrap_used))] -use std::{borrow::Cow, fmt, str::FromStr, sync::Arc, time::Duration}; +use std::{borrow::Cow, collections::BTreeMap, fmt, str::FromStr, sync::Arc, time::Duration}; use anyhow::{Result, bail}; use sentry::{ @@ -173,10 +173,7 @@ impl Telemetry { scope.set_context("os", ctx); } - scope.set_user(Some(User { - id: Some(firezone_id), - ..User::default() - })); + scope.set_user(Some(compute_user(firezone_id))); }); self.inner.replace(inner); sentry::start_session(); @@ -216,31 +213,31 @@ impl Telemetry { user.other.insert("account_slug".to_owned(), slug.into()); }); } +} - pub fn set_firezone_id(id: String) { - update_user({ - let id = id.clone(); - move |user| { - user.id = Some(id.clone()); +/// Computes the [`User`] scope based on the contents of `firezone_id`. +/// +/// If `firezone_id` looks like a UUID, we hash and hex-encode it. +/// This will align the ID with what we see in the portal. +/// +/// If it is not a UUID, it is already from a newer installation of Firezone +/// where the ID is sent as-is. +/// +/// As a result, this will allow us to always filter the user by the hex-encoded ID. +fn compute_user(firezone_id: String) -> User { + if uuid::Uuid::from_str(&firezone_id).is_ok() { + let encoded_id = hex::encode(sha2::Sha256::digest(firezone_id)); - if uuid::Uuid::from_str(&id).is_ok() { - user.other.insert( - "external_id".to_owned(), - serde_json::Value::String(hex::encode(sha2::Sha256::digest(&id))), - ); - } - } - }); - - let Some(client) = sentry::Hub::main().client() else { - return; + return User { + id: Some(encoded_id.clone()), + other: BTreeMap::from([("uuid".to_owned(), serde_json::Value::String(encoded_id))]), + ..User::default() }; + } - let Some(env) = client.options().environment.as_ref() else { - return; // Nothing to do if we don't have an environment set. - }; - - feature_flags::reevaluate(id, env); + User { + id: Some(firezone_id), + ..User::default() } }