use std::collections::HashMap;
use re_build_info::{BuildInfo, CrateVersion};
use url::Url;
use crate::{AnalyticsEvent, Event, EventKind, Properties, Property};
pub struct CrashPanic {
pub build_info: BuildInfo,
pub callstack: String,
pub message: Option<String>,
pub file_line: Option<String>,
}
impl Event for CrashPanic {
const NAME: &'static str = "crash-panic";
}
impl Properties for CrashPanic {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
build_info,
callstack,
message,
file_line,
} = self;
build_info.serialize(event);
event.insert("callstack", callstack);
event.insert_opt("message", message);
event.insert_opt("file_line", file_line);
}
}
pub struct CrashSignal {
pub build_info: BuildInfo,
pub signal: String,
pub callstack: String,
}
impl Event for CrashSignal {
const NAME: &'static str = "crash-signal";
}
impl Properties for CrashSignal {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
build_info,
signal,
callstack,
} = self;
build_info.serialize(event);
event.insert("signal", signal.clone());
event.insert("callstack", callstack.clone());
}
}
pub struct Identify {
pub build_info: re_build_info::BuildInfo,
pub rust_version: Option<String>,
pub llvm_version: Option<String>,
pub python_version: Option<String>,
pub opt_in_metadata: HashMap<String, Property>,
}
impl Event for Identify {
const NAME: &'static str = "$identify";
const KIND: EventKind = EventKind::Identify;
}
impl Properties for Identify {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
build_info,
rust_version,
llvm_version,
python_version,
opt_in_metadata,
} = self;
build_info.serialize(event);
event.insert_opt("rust_version", rust_version);
event.insert_opt("llvm_version", llvm_version);
event.insert_opt("python_version", python_version);
for (name, value) in opt_in_metadata {
event.insert(name, value);
}
}
}
pub struct ViewerRuntimeInformation {
pub is_docker: bool,
pub is_wsl: bool,
pub graphics_adapter_backend: String,
pub re_renderer_device_tier: String,
pub screen_info: ScreenInfo,
}
impl Properties for ViewerRuntimeInformation {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
is_docker,
is_wsl,
graphics_adapter_backend,
re_renderer_device_tier,
screen_info,
} = self;
event.insert("is_docker", is_docker);
event.insert("is_wsl", is_wsl);
event.insert("graphics_adapter_backend", graphics_adapter_backend);
event.insert("re_renderer_device_tier", re_renderer_device_tier);
screen_info.serialize(event);
}
}
pub struct ScreenInfo {
pub pixels_per_point: f32,
pub native_pixels_per_point: Option<f32>,
pub zoom_factor: f32,
}
impl Properties for ScreenInfo {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
pixels_per_point,
native_pixels_per_point,
zoom_factor,
} = self;
event.insert("pixels_per_point", pixels_per_point);
event.insert_opt("native_pixels_per_point", native_pixels_per_point);
event.insert("zoom_factor", zoom_factor);
}
}
pub struct StoreInfo {
pub application_id: Id,
pub recording_id: Id,
pub store_source: String,
pub store_version: String,
pub rust_version: Option<String>,
pub llvm_version: Option<String>,
pub python_version: Option<String>,
pub app_id_starts_with_rerun_example: bool,
}
#[derive(Clone)]
pub enum Id {
Official(String),
Hashed(Property),
}
impl From<Id> for Property {
fn from(val: Id) -> Self {
match val {
Id::Official(id) => Self::String(id),
Id::Hashed(id) => id,
}
}
}
pub struct ServeWasm;
impl Event for ServeWasm {
const NAME: &'static str = "serve_wasm";
}
impl Properties for ServeWasm {
}
pub struct ViewerStarted {
pub url: Option<String>,
pub app_env: &'static str,
pub runtime_info: ViewerRuntimeInformation,
}
impl Event for ViewerStarted {
const NAME: &'static str = "viewer_started";
}
const RERUN_DOMAINS: [&str; 1] = ["rerun.io"];
fn extract_root_domain(url: &str) -> Option<String> {
let parsed = Url::parse(url).ok()?;
let domain = parsed.domain()?;
let parts = domain.split('.').collect::<Vec<_>>();
if parts.len() >= 2 {
Some(parts[parts.len() - 2..].join("."))
} else {
None
}
}
fn add_sanitized_url_properties(event: &mut AnalyticsEvent, url: Option<String>) {
let Some(root_domain) = url.as_ref().and_then(|url| extract_root_domain(url)) else {
return;
};
if RERUN_DOMAINS.contains(&root_domain.as_str()) {
event.insert_opt("rerun_url", url);
}
let hashed = Property::from(root_domain).hashed();
event.insert("hashed_root_domain", hashed);
}
impl Properties for ViewerStarted {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
url,
app_env,
runtime_info,
} = self;
event.insert("app_env", app_env);
add_sanitized_url_properties(event, url);
runtime_info.serialize(event);
}
}
pub struct OpenRecording {
pub url: Option<String>,
pub app_env: &'static str,
pub store_info: Option<StoreInfo>,
pub data_source: Option<&'static str>,
}
impl Event for OpenRecording {
const NAME: &'static str = "open_recording";
}
impl Properties for OpenRecording {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
url,
app_env,
store_info,
data_source,
} = self;
add_sanitized_url_properties(event, url);
event.insert("app_env", app_env);
if let Some(store_info) = store_info {
let StoreInfo {
application_id,
recording_id,
store_source,
store_version,
rust_version,
llvm_version,
python_version,
app_id_starts_with_rerun_example,
} = store_info;
event.insert("application_id", application_id);
event.insert("recording_id", recording_id);
event.insert("store_source", store_source);
event.insert("store_version", store_version);
event.insert_opt("rust_version", rust_version);
event.insert_opt("llvm_version", llvm_version);
event.insert_opt("python_version", python_version);
event.insert(
"app_id_starts_with_rerun_example",
app_id_starts_with_rerun_example,
);
}
if let Some(data_source) = data_source {
event.insert("data_source", data_source);
}
}
}
pub struct HelpButtonFirstClicked {}
impl Event for HelpButtonFirstClicked {
const NAME: &'static str = "help-button-clicked";
}
impl Properties for HelpButtonFirstClicked {
fn serialize(self, _event: &mut AnalyticsEvent) {
let Self {} = self;
}
}
pub struct SettingsOpened {}
impl Event for SettingsOpened {
const NAME: &'static str = "settings-opened";
}
impl Properties for SettingsOpened {
fn serialize(self, _event: &mut AnalyticsEvent) {
let Self {} = self;
}
}
pub struct SetPersonProperty {
pub email: String,
pub organization_id: String,
}
impl Event for SetPersonProperty {
const NAME: &'static str = "$set";
const KIND: EventKind = EventKind::SetPersonProperties;
}
impl Properties for SetPersonProperty {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
email,
organization_id,
} = self;
event.insert("email", email);
event.insert("organization_id", organization_id);
}
}
pub struct LoadDataSource {
pub source_type: &'static str,
pub file_extension: Option<String>,
pub file_source: Option<&'static str>,
pub started_successfully: bool,
}
impl Event for LoadDataSource {
const NAME: &'static str = "load_data_source";
}
impl Properties for LoadDataSource {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
source_type,
file_extension,
file_source,
started_successfully,
} = self;
event.insert("source_type", source_type);
event.insert_opt("file_extension", file_extension);
event.insert_opt("file_source", file_source.map(|s| s.to_owned()));
event.insert("started_successfully", started_successfully);
}
}
#[derive(Default)]
pub struct CliCommandInvoked {
pub command: &'static str,
pub subcommand: Option<&'static str>,
pub web_viewer: bool,
pub serve_web: bool,
pub serve_grpc: bool,
pub connect: bool,
pub save: bool,
pub screenshot_to: bool,
pub newest_first: bool,
pub persist_state_disabled: bool,
pub profile: bool,
pub expect_data_soon: bool,
pub hide_welcome_screen: bool,
pub detach_process: bool,
pub test_receive: bool,
}
impl Event for CliCommandInvoked {
const NAME: &'static str = "cli_command_invoked";
}
impl Properties for CliCommandInvoked {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
command,
subcommand,
web_viewer,
serve_web,
serve_grpc,
connect,
save,
screenshot_to,
newest_first,
persist_state_disabled,
profile,
expect_data_soon,
hide_welcome_screen,
detach_process,
test_receive,
} = self;
event.insert("command", command);
event.insert_opt("subcommand", subcommand.map(|s| s.to_owned()));
event.insert("web_viewer", web_viewer);
event.insert("serve_web", serve_web);
event.insert("serve_grpc", serve_grpc);
event.insert("connect", connect);
event.insert("save", save);
event.insert("screenshot_to", screenshot_to);
event.insert("newest_first", newest_first);
event.insert("persist_state_disabled", persist_state_disabled);
event.insert("profile", profile);
event.insert("expect_data_soon", expect_data_soon);
event.insert("hide_welcome_screen", hide_welcome_screen);
event.insert("detach_process", detach_process);
event.insert("test_receive", test_receive);
}
}
pub struct WelcomeScreenNavigation {
pub card_type: String,
pub destination: String,
pub cta_cloud: bool,
pub is_logged_in: bool,
pub has_server: bool,
}
impl Event for WelcomeScreenNavigation {
const NAME: &'static str = "welcome_navigation";
}
impl Properties for WelcomeScreenNavigation {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
card_type,
destination,
cta_cloud,
is_logged_in,
has_server,
} = self;
event.insert("card_type", card_type);
event.insert("destination", destination);
event.insert("cta_cloud", cta_cloud);
event.insert("is_logged_in", is_logged_in);
event.insert("has_server", has_server);
event.insert("rerun_version", CrateVersion::LOCAL.to_string());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_root_domain() {
assert_eq!(
extract_root_domain("https://rerun.io"),
Some("rerun.io".to_owned())
);
assert_eq!(
extract_root_domain("https://ReRun.io"),
Some("rerun.io".to_owned())
);
assert_eq!(
extract_root_domain("http://app.rerun.io"),
Some("rerun.io".to_owned())
);
assert_eq!(
extract_root_domain(
"https://www.rerun.io/viewer?url=https://app.rerun.io/version/0.15.1/examples/detect_and_track_objects.rrd"
),
Some("rerun.io".to_owned())
);
assert_eq!(
extract_root_domain("http://localhost:9090/?url=rerun%2Bhttp://localhost:9877"),
None
);
assert_eq!(
extract_root_domain("http://127.0.0.1:9090/?url=rerun%2Bhttp://localhost:9877"),
None
);
assert_eq!(extract_root_domain("rerun.io"), None);
assert_eq!(extract_root_domain("https:/rerun"), None);
assert_eq!(extract_root_domain("https://rerun"), None);
}
}