systemprompt_analytics/models/
events.rs1use serde::{Deserialize, Serialize};
7use systemprompt_identifiers::ContentId;
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum AnalyticsEventType {
12 PageView,
13 PageExit,
14 LinkClick,
15 Scroll,
16 Engagement,
17 Conversion,
18 #[serde(untagged)]
19 Custom(String),
20}
21
22impl AnalyticsEventType {
23 pub const fn as_str(&self) -> &str {
24 match self {
25 Self::PageView => "page_view",
26 Self::PageExit => "page_exit",
27 Self::LinkClick => "link_click",
28 Self::Scroll => "scroll",
29 Self::Engagement => "engagement",
30 Self::Conversion => "conversion",
31 Self::Custom(s) => s.as_str(),
32 }
33 }
34
35 pub const fn category(&self) -> &str {
36 match self {
37 Self::PageView | Self::PageExit => "navigation",
38 Self::LinkClick => "interaction",
39 Self::Scroll | Self::Engagement => "engagement",
40 Self::Conversion => "conversion",
41 Self::Custom(_) => "custom",
42 }
43 }
44}
45
46impl std::fmt::Display for AnalyticsEventType {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 write!(f, "{}", self.as_str())
49 }
50}
51
52#[derive(Debug, Clone, Deserialize)]
53pub struct CreateAnalyticsEventInput {
54 pub event_type: AnalyticsEventType,
55 pub page_url: String,
56 #[serde(default)]
57 pub content_id: Option<ContentId>,
58 #[serde(default)]
59 pub slug: Option<String>,
60 #[serde(default)]
61 pub referrer: Option<String>,
62 #[serde(default)]
63 pub data: Option<serde_json::Value>,
64}
65
66#[derive(Debug, Clone, Deserialize)]
67pub struct CreateAnalyticsEventBatchInput {
68 pub events: Vec<CreateAnalyticsEventInput>,
69}
70
71#[derive(Debug, Clone, Serialize)]
72pub struct AnalyticsEventCreated {
73 pub id: String,
74 pub event_type: String,
75}
76
77#[derive(Debug, Clone, Serialize)]
78pub struct AnalyticsEventBatchResponse {
79 pub recorded: usize,
80 pub events: Vec<AnalyticsEventCreated>,
81}
82
83#[derive(Debug, Clone, Default, Serialize, Deserialize)]
84pub struct EngagementEventData {
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub max_scroll_depth: Option<i32>,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub time_on_page_ms: Option<i64>,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub time_to_first_interaction_ms: Option<i64>,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 pub time_to_first_scroll_ms: Option<i64>,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub click_count: Option<i32>,
95 #[serde(skip_serializing_if = "Option::is_none")]
96 pub scroll_velocity_avg: Option<f32>,
97 #[serde(skip_serializing_if = "Option::is_none")]
98 pub scroll_direction_changes: Option<i32>,
99 #[serde(skip_serializing_if = "Option::is_none")]
100 pub mouse_move_distance_px: Option<i32>,
101 #[serde(skip_serializing_if = "Option::is_none")]
102 pub keyboard_events: Option<i32>,
103 #[serde(skip_serializing_if = "Option::is_none")]
104 pub copy_events: Option<i32>,
105 #[serde(skip_serializing_if = "Option::is_none")]
106 pub focus_time_ms: Option<i32>,
107 #[serde(skip_serializing_if = "Option::is_none")]
108 pub blur_count: Option<i32>,
109 #[serde(skip_serializing_if = "Option::is_none")]
110 pub tab_switches: Option<i32>,
111 #[serde(skip_serializing_if = "Option::is_none")]
112 pub visible_time_ms: Option<i64>,
113 #[serde(skip_serializing_if = "Option::is_none")]
114 pub hidden_time_ms: Option<i64>,
115 #[serde(skip_serializing_if = "Option::is_none")]
116 pub is_rage_click: Option<bool>,
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub is_dead_click: Option<bool>,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub reading_pattern: Option<String>,
121}
122
123#[derive(Debug, Clone, Default, Serialize, Deserialize)]
124pub struct LinkClickEventData {
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub target_url: Option<String>,
127 #[serde(skip_serializing_if = "Option::is_none")]
128 pub link_text: Option<String>,
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub link_position: Option<String>,
131 #[serde(skip_serializing_if = "Option::is_none")]
132 pub is_external: Option<bool>,
133}
134
135#[derive(Debug, Clone, Default, Serialize, Deserialize)]
136pub struct ScrollEventData {
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub depth: Option<i32>,
139 #[serde(skip_serializing_if = "Option::is_none")]
140 pub milestone: Option<i32>,
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub direction: Option<String>,
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub velocity: Option<f32>,
145}
146
147#[derive(Debug, Clone, Default, Serialize, Deserialize)]
148pub struct ConversionEventData {
149 #[serde(skip_serializing_if = "Option::is_none")]
150 pub goal_name: Option<String>,
151 #[serde(skip_serializing_if = "Option::is_none")]
152 pub goal_value: Option<f64>,
153 #[serde(skip_serializing_if = "Option::is_none")]
154 pub funnel_step: Option<i32>,
155}