Skip to content

Commit 388ee1e

Browse files
authored
Merge pull request #22 from PostHog/olly_group_support
feat: group support, anon event support
2 parents 612d772 + 2a8988a commit 388ee1e

File tree

2 files changed

+81
-25
lines changed

2 files changed

+81
-25
lines changed

Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "posthog-rs"
33
license = "MIT"
4-
version = "0.3.2"
4+
version = "0.3.3"
55
authors = ["christos <[email protected]>"]
66
description = "An unofficial Rust client for Posthog (https://posthog.com/)."
77
repository = "https://github.com/openquery-io/posthog-rs"
@@ -18,11 +18,13 @@ chrono = { version = "0.4.19", features = ["serde"] }
1818
serde_json = "1.0.64"
1919
semver = "1.0.24"
2020
derive_builder = "0.20.2"
21+
uuid = { version = "1.13.2", features = ["serde", "v7"] }
2122

2223
[dev-dependencies]
2324
dotenv = "0.15.0"
2425
ctor = "0.1.26"
2526

2627
[features]
28+
default = ["async-client"]
2729
e2e-test = []
2830
async-client = []

src/event.rs

+78-24
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,50 @@ use std::collections::HashMap;
33
use chrono::NaiveDateTime;
44
use semver::Version;
55
use serde::Serialize;
6+
use uuid::Uuid;
67

78
use crate::Error;
89

910
#[derive(Serialize, Debug, PartialEq, Eq)]
1011
pub struct Event {
1112
event: String,
12-
properties: Properties,
13-
timestamp: Option<NaiveDateTime>,
14-
}
15-
16-
#[derive(Serialize, Debug, PartialEq, Eq)]
17-
pub struct Properties {
13+
#[serde(rename = "$distinct_id")]
1814
distinct_id: String,
19-
props: HashMap<String, serde_json::Value>,
15+
properties: HashMap<String, serde_json::Value>,
16+
groups: HashMap<String, String>,
17+
timestamp: Option<NaiveDateTime>,
2018
}
2119

22-
impl Properties {
23-
fn new<S: Into<String>>(distinct_id: S) -> Self {
20+
impl Event {
21+
/// Capture a new identified event. Unless you have a distinct ID you can
22+
/// associate with a user, you probably want to use `new_anon` instead.
23+
pub fn new<S: Into<String>>(event: S, distinct_id: S) -> Self {
2424
Self {
25+
event: event.into(),
2526
distinct_id: distinct_id.into(),
26-
props: Default::default(),
27+
properties: HashMap::new(),
28+
groups: HashMap::new(),
29+
timestamp: None,
2730
}
2831
}
29-
}
3032

31-
impl Event {
32-
pub fn new<S: Into<String>>(event: S, distinct_id: S) -> Self {
33-
Self {
33+
/// Capture a new anonymous event.
34+
/// See https://posthog.com/docs/data/anonymous-vs-identified-events#how-to-capture-anonymous-events
35+
pub fn new_anon<S: Into<String>>(event: S) -> Self {
36+
let mut res = Self {
3437
event: event.into(),
35-
properties: Properties::new(distinct_id),
38+
distinct_id: Uuid::now_v7().to_string(),
39+
properties: HashMap::new(),
40+
groups: HashMap::new(),
3641
timestamp: None,
37-
}
42+
};
43+
res.insert_prop("$process_person_profile", false)
44+
.expect("bools are safe for serde");
45+
res
3846
}
3947

48+
/// Add a property to the event
49+
///
4050
/// Errors if `prop` fails to serialize
4151
pub fn insert_prop<K: Into<String>, P: Serialize>(
4252
&mut self,
@@ -45,54 +55,87 @@ impl Event {
4555
) -> Result<(), Error> {
4656
let as_json =
4757
serde_json::to_value(prop).map_err(|e| Error::Serialization(e.to_string()))?;
48-
let _ = self.properties.props.insert(key.into(), as_json);
58+
let _ = self.properties.insert(key.into(), as_json);
4959
Ok(())
5060
}
61+
62+
/// Capture this as a group event. See https://posthog.com/docs/product-analytics/group-analytics#how-to-capture-group-events
63+
/// Note that group events cannot be personless, and will be automatically upgraded to include person profile processing if
64+
/// they were anonymous. This might lead to "empty" person profiles being created.
65+
pub fn add_group(&mut self, group_name: &str, group_id: &str) {
66+
// You cannot disable person profile processing for groups
67+
self.insert_prop("$process_person_profile", true)
68+
.expect("bools are safe for serde");
69+
self.groups.insert(group_name.into(), group_id.into());
70+
}
5171
}
5272

5373
// This exists so that the client doesn't have to specify the API key over and over
5474
#[derive(Serialize)]
5575
pub struct InnerEvent {
5676
api_key: String,
77+
#[serde(skip_serializing_if = "Option::is_none")]
78+
uuid: Option<Uuid>,
5779
event: String,
58-
properties: Properties,
80+
#[serde(rename = "$distinct_id")]
81+
distinct_id: String,
82+
properties: HashMap<String, serde_json::Value>,
5983
timestamp: Option<NaiveDateTime>,
6084
}
6185

6286
impl InnerEvent {
6387
pub fn new(event: Event, api_key: String) -> Self {
88+
Self::new_with_uuid(event, api_key, None)
89+
}
90+
91+
pub fn new_with_uuid(event: Event, api_key: String, uuid: Option<Uuid>) -> Self {
6492
let mut properties = event.properties;
6593

6694
// Add $lib_name and $lib_version to the properties
67-
properties.props.insert(
95+
properties.insert(
6896
"$lib_name".into(),
6997
serde_json::Value::String("posthog-rs".into()),
7098
);
7199

72100
let version_str = env!("CARGO_PKG_VERSION");
73-
properties.props.insert(
101+
properties.insert(
74102
"$lib_version".into(),
75103
serde_json::Value::String(version_str.into()),
76104
);
77105

78106
if let Ok(version) = version_str.parse::<Version>() {
79-
properties.props.insert(
107+
properties.insert(
80108
"$lib_version__major".into(),
81109
serde_json::Value::Number(version.major.into()),
82110
);
83-
properties.props.insert(
111+
properties.insert(
84112
"$lib_version__minor".into(),
85113
serde_json::Value::Number(version.minor.into()),
86114
);
87-
properties.props.insert(
115+
properties.insert(
88116
"$lib_version__patch".into(),
89117
serde_json::Value::Number(version.patch.into()),
90118
);
91119
}
92120

121+
if !event.groups.is_empty() {
122+
properties.insert(
123+
"$groups".into(),
124+
serde_json::Value::Object(
125+
event
126+
.groups
127+
.into_iter()
128+
.map(|(k, v)| (k, serde_json::Value::String(v)))
129+
.collect(),
130+
),
131+
);
132+
}
133+
93134
Self {
94135
api_key,
136+
uuid,
95137
event: event.event,
138+
distinct_id: event.distinct_id,
96139
properties,
97140
timestamp: event.timestamp,
98141
}
@@ -114,10 +157,21 @@ pub mod tests {
114157
let inner_event = InnerEvent::new(event, api_key);
115158

116159
// Assert
117-
let props = &inner_event.properties.props;
160+
let props = &inner_event.properties;
118161
assert_eq!(
119162
props.get("$lib_name"),
120163
Some(&serde_json::Value::String("posthog-rs".to_string()))
121164
);
122165
}
166+
167+
#[test]
168+
fn event_structure_is_correct() {
169+
let mut event = Event::new("unit test event", "1234");
170+
event.insert_prop("key1", "value1").unwrap();
171+
let api_key = "test_api_key".to_string();
172+
let inner_event = InnerEvent::new(event, api_key);
173+
let payload = serde_json::to_string(&inner_event).unwrap();
174+
175+
println!("{}", payload);
176+
}
123177
}

0 commit comments

Comments
 (0)