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