use std::time::SystemTime;
use teksilo_core::telemetry::{Event, EventCategory, IntentSource, Prop, PropValue, UsageReporter};
pub const EVENT_SCHEMA_VERSION: u32 = 1;
pub fn emit_intent_dispatched(
reporter: &dyn UsageReporter,
install_id: Option<&str>,
session_id: &str,
name: &'static str,
source: IntentSource,
) {
let props = [
Prop {
key: "name",
value: PropValue::StaticStr(name),
},
Prop {
key: "source",
value: PropValue::Enum {
variant: source.as_str(),
},
},
];
let event = Event {
name: "intent.dispatched",
category: EventCategory::Intent,
timestamp: SystemTime::now(),
install_id,
session_id,
schema_version: EVENT_SCHEMA_VERSION,
props: &props,
};
reporter.record(&event);
}
#[allow(clippy::too_many_arguments)]
pub fn emit_lifecycle_app_started(
reporter: &dyn UsageReporter,
install_id: Option<&str>,
session_id: &str,
app_version: &str,
teksilo_version: &str,
os: AppOs,
arch: AppArch,
locale: &str,
theme_kind: ThemeKind,
) {
let props = [
Prop {
key: "app_version",
value: PropValue::BoundedStr(app_version),
},
Prop {
key: "teksilo_version",
value: PropValue::BoundedStr(teksilo_version),
},
Prop {
key: "os",
value: PropValue::Enum {
variant: os.as_str(),
},
},
Prop {
key: "arch",
value: PropValue::Enum {
variant: arch.as_str(),
},
},
Prop {
key: "locale",
value: PropValue::BoundedStr(locale),
},
Prop {
key: "theme_kind",
value: PropValue::Enum {
variant: theme_kind.as_str(),
},
},
];
let event = Event {
name: "lifecycle.app_started",
category: EventCategory::Lifecycle,
timestamp: SystemTime::now(),
install_id,
session_id,
schema_version: EVENT_SCHEMA_VERSION,
props: &props,
};
reporter.record(&event);
}
pub fn emit_lifecycle_app_exited(
reporter: &dyn UsageReporter,
install_id: Option<&str>,
session_id: &str,
duration: SessionDurationBucket,
) {
let props = [Prop {
key: "session_duration_bucket",
value: PropValue::Enum {
variant: duration.as_str(),
},
}];
let event = Event {
name: "lifecycle.app_exited",
category: EventCategory::Lifecycle,
timestamp: SystemTime::now(),
install_id,
session_id,
schema_version: EVENT_SCHEMA_VERSION,
props: &props,
};
reporter.record(&event);
}
pub fn emit_widget_census(
reporter: &dyn UsageReporter,
install_id: Option<&str>,
session_id: &str,
histogram: &[(&'static str, u32)],
total: u32,
) {
let props = [
Prop {
key: "widget_count_by_type",
value: PropValue::HistogramStrU32(histogram),
},
Prop {
key: "total_widgets",
value: PropValue::U32(total),
},
];
let event = Event {
name: "widget.census",
category: EventCategory::Census,
timestamp: SystemTime::now(),
install_id,
session_id,
schema_version: EVENT_SCHEMA_VERSION,
props: &props,
};
reporter.record(&event);
}
pub fn emit_window_lifecycle(
reporter: &dyn UsageReporter,
install_id: Option<&str>,
session_id: &str,
opened: bool,
kind: WindowKind,
) {
let props = [Prop {
key: "kind",
value: PropValue::Enum {
variant: kind.as_str(),
},
}];
let name = if opened {
"window.opened"
} else {
"window.closed"
};
let event = Event {
name,
category: EventCategory::Lifecycle,
timestamp: SystemTime::now(),
install_id,
session_id,
schema_version: EVENT_SCHEMA_VERSION,
props: &props,
};
reporter.record(&event);
}
#[derive(Copy, Clone, Debug)]
pub enum AppOs {
Linux,
Macos,
Windows,
Freebsd,
Other,
}
impl AppOs {
pub fn detect() -> Self {
match std::env::consts::OS {
"linux" => Self::Linux,
"macos" => Self::Macos,
"windows" => Self::Windows,
"freebsd" => Self::Freebsd,
_ => Self::Other,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::Linux => "linux",
Self::Macos => "macos",
Self::Windows => "windows",
Self::Freebsd => "freebsd",
Self::Other => "other",
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum AppArch {
X86_64,
Aarch64,
Other,
}
impl AppArch {
pub fn detect() -> Self {
match std::env::consts::ARCH {
"x86_64" => Self::X86_64,
"aarch64" => Self::Aarch64,
_ => Self::Other,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::X86_64 => "x86_64",
Self::Aarch64 => "aarch64",
Self::Other => "other",
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum ThemeKind {
Light,
Dark,
Custom,
}
impl ThemeKind {
pub fn as_str(self) -> &'static str {
match self {
Self::Light => "light",
Self::Dark => "dark",
Self::Custom => "custom",
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum SessionDurationBucket {
Under1m,
M1to5,
M5to30,
M30to2h,
H2to8,
Over8h,
}
impl SessionDurationBucket {
pub fn from_seconds(s: u64) -> Self {
match s {
0..=59 => Self::Under1m,
60..=299 => Self::M1to5,
300..=1799 => Self::M5to30,
1800..=7199 => Self::M30to2h,
7200..=28799 => Self::H2to8,
_ => Self::Over8h,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::Under1m => "under_1m",
Self::M1to5 => "m1_5",
Self::M5to30 => "m5_30",
Self::M30to2h => "m30_2h",
Self::H2to8 => "h2_8",
Self::Over8h => "over_8h",
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum WindowKind {
Main,
Dialog,
Popover,
Secondary,
}
impl WindowKind {
pub fn as_str(self) -> &'static str {
match self {
Self::Main => "main",
Self::Dialog => "dialog",
Self::Popover => "popover",
Self::Secondary => "secondary",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn session_duration_buckets_cover_canonical_ranges() {
assert!(matches!(
SessionDurationBucket::from_seconds(30),
SessionDurationBucket::Under1m
));
assert!(matches!(
SessionDurationBucket::from_seconds(60),
SessionDurationBucket::M1to5
));
assert!(matches!(
SessionDurationBucket::from_seconds(7200),
SessionDurationBucket::H2to8
));
assert!(matches!(
SessionDurationBucket::from_seconds(1_000_000),
SessionDurationBucket::Over8h
));
}
}