pub struct CrashPanic {
pub build_info: BuildInfo,
pub callstack: String,
pub message: Option<String>,
pub file_line: Option<String>,
}
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>,
}
pub struct ViewerStarted {
pub url: Option<String>,
pub app_env: &'static str,
}
pub struct OpenRecording {
pub url: Option<String>,
pub app_env: &'static str,
pub store_info: Option<StoreInfo>,
pub data_source: Option<&'static str>,
}
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 is_official_example: bool,
pub app_id_starts_with_rerun_example: bool,
}
#[derive(Clone)]
pub enum Id {
Official(String),
Hashed(Property),
}
pub struct ServeWasm;
impl Event for ServeWasm {
const NAME: &'static str = "serve_wasm";
}
impl Properties for ServeWasm {
}
use std::collections::HashMap;
use re_build_info::BuildInfo;
use url::Url;
use crate::{AnalyticsEvent, Event, EventKind, Properties, Property};
impl From<Id> for Property {
fn from(val: Id) -> Self {
match val {
Id::Official(id) => Property::String(id),
Id::Hashed(id) => id,
}
}
}
impl Event for Identify {
const NAME: &'static str = "$identify";
const KIND: EventKind = EventKind::Update;
}
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);
}
}
}
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 } = self;
event.insert("app_env", app_env);
add_sanitized_url_properties(event, url);
}
}
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,
is_official_example,
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("is_official_example", is_official_example);
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);
}
}
}
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());
}
}
#[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=ws://localhost:9877"),
None
);
assert_eq!(
extract_root_domain("http://127.0.0.1:9090/?url=ws://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);
}
}