Skip to content

Commit 19bbc44

Browse files
authored
Add posthog recordings and events (#6310)
1 parent 08ee62a commit 19bbc44

File tree

4 files changed

+111
-6
lines changed

4 files changed

+111
-6
lines changed

packages/reflex-ui-shared/src/reflex_ui_shared/telemetry/pixels.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from reflex_ui.blocks.telemetry import (
44
get_default_telemetry_script,
55
get_google_analytics_trackers,
6+
get_posthog_trackers,
67
get_unify_trackers,
78
gtag_report_conversion,
89
)
@@ -23,4 +24,7 @@ def get_pixel_website_trackers() -> list[rx.Component]:
2324
),
2425
get_unify_trackers(),
2526
get_default_telemetry_script(),
27+
get_posthog_trackers(
28+
project_id="phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb"
29+
),
2630
]

packages/reflex-ui/src/reflex_ui/blocks/demo_form.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
based on company size.
66
"""
77

8+
from typing import Any
9+
810
import reflex as rx
911
from reflex.event import EventType
1012
from reflex.experimental.client_state import ClientStateVar
1113
from reflex.vars.base import get_unique_variable_name
14+
from reflex_ui.blocks.telemetry.posthog import track_demo_form_posthog_submission
1215
from reflex_ui.components.base.button import button
1316
from reflex_ui.components.base.dialog import dialog
1417
from reflex_ui.components.base.input import input
@@ -102,6 +105,15 @@ def validate_email(self, email: str):
102105
else:
103106
yield demo_form_error_message.push("")
104107

108+
@rx.event
109+
def track_demo_form_posthog(self, form_data: dict[str, Any]):
110+
"""Send demo form fields to PostHog (identify + capture) in the browser.
111+
112+
Returns:
113+
Event that runs PostHog identify and capture in the browser.
114+
"""
115+
return track_demo_form_posthog_submission(form_data)
116+
105117

106118
def input_field(
107119
label: str,
@@ -344,8 +356,10 @@ def demo_form(id_prefix: str = "", **props) -> rx.Component:
344356
"@container flex flex-col lg:gap-6 gap-2 p-6",
345357
props.pop("class_name", ""),
346358
),
347-
# Close the dialog when the form is submitted
348-
on_submit=demo_form_open_cs.set_value(False),
359+
on_submit=[
360+
DemoFormStateUI.track_demo_form_posthog,
361+
rx.call_function(demo_form_open_cs.set_value(False)),
362+
],
349363
data_default_form_id="965991",
350364
**props,
351365
)

packages/reflex-ui/src/reflex_ui/blocks/intro_form.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
from typing import Any
77

88
import reflex as rx
9-
from reflex.event import EventType
9+
from reflex.event import EventType, IndividualEventType
1010
from reflex.experimental.client_state import ClientStateVar
1111
from reflex.vars.base import get_unique_variable_name
12+
from reflex_ui.blocks.telemetry.posthog import track_intro_form_posthog_submission
1213
from reflex_ui.components.base.button import button
1314
from reflex_ui.components.base.dialog import dialog
1415
from reflex_ui.components.base.input import input
@@ -103,6 +104,15 @@ def validate_email(self, email: str):
103104
else:
104105
yield intro_form_error_message.push("")
105106

107+
@rx.event
108+
def track_intro_form_posthog(self, form_data: dict[str, Any]):
109+
"""Send intro form fields to PostHog (identify + capture) in the browser.
110+
111+
Returns:
112+
Event that runs PostHog identify and capture in the browser.
113+
"""
114+
return track_intro_form_posthog_submission(form_data)
115+
106116

107117
def input_field(
108118
label: str,
@@ -258,7 +268,7 @@ def select_field(
258268

259269
def intro_form(
260270
id_prefix: str = "",
261-
on_submit: EventType[dict[str, Any]] | EventType[()] | None = None,
271+
on_submit: EventType[dict[str, Any]] | None = None,
262272
**props,
263273
) -> rx.Component:
264274
"""Create and return the intro form component.
@@ -278,6 +288,11 @@ def intro_form(
278288
"""
279289
prefix = id_prefix or get_unique_variable_name()
280290
email_id = f"{prefix}_user_email"
291+
292+
extra: list[IndividualEventType[dict[str, Any]]] = (
293+
on_submit if isinstance(on_submit, list) else [on_submit] if on_submit else []
294+
)
295+
281296
form = rx.el.form(
282297
rx.el.div(
283298
input_field("First name", "John", "first_name", "text", True),
@@ -357,7 +372,7 @@ def intro_form(
357372
"@container flex flex-col lg:gap-6 gap-2 p-6",
358373
props.pop("class_name", ""),
359374
),
360-
on_submit=on_submit,
375+
on_submit=[IntroFormStateUI.track_intro_form_posthog, *extra],
361376
**props,
362377
)
363378
return rx.fragment(
@@ -368,7 +383,7 @@ def intro_form(
368383
def intro_form_dialog(
369384
trigger: rx.Component | None = None,
370385
id_prefix: str = "",
371-
on_submit: EventType[dict[str, Any]] | EventType[()] | None = None,
386+
on_submit: EventType[dict[str, Any]] | None = None,
372387
**props,
373388
) -> rx.Component:
374389
"""Return a intro form dialog container element.

packages/reflex-ui/src/reflex_ui/blocks/telemetry/posthog.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""PostHog analytics tracking integration for Reflex applications."""
22

33
import json
4+
from typing import Any
45

56
import reflex as rx
67

@@ -60,6 +61,77 @@ def identify_posthog_user(user_id: str) -> rx.event.EventSpec:
6061
)
6162

6263

64+
def _track_form_posthog(
65+
event_name: str,
66+
form_data: dict[str, Any],
67+
allowed_keys: set[str],
68+
) -> rx.event.EventSpec:
69+
"""Identify the submitter and capture a form event in PostHog.
70+
71+
Args:
72+
event_name: PostHog event name to capture.
73+
form_data: Submitted form fields as a string-keyed dict.
74+
allowed_keys: Set of keys to include from form_data.
75+
76+
Returns:
77+
Event that runs PostHog identify and capture in the browser.
78+
"""
79+
filtered = {k: v for k, v in form_data.items() if k in allowed_keys}
80+
props_json = json.dumps(filtered)
81+
82+
return rx.call_script(
83+
f"""
84+
if (typeof posthog !== 'undefined') {{
85+
const props = {props_json};
86+
const distinctId = props.email || ('anon_' + String(Date.now()));
87+
posthog.identify(distinctId, {{
88+
email: props.email,
89+
first_name: props.first_name,
90+
last_name: props.last_name,
91+
job_title: props.job_title,
92+
company_name: props.company_name,
93+
}});
94+
posthog.capture('{event_name}', props);
95+
}}
96+
"""
97+
)
98+
99+
100+
_COMMON_KEYS = {
101+
"email",
102+
"first_name",
103+
"last_name",
104+
"job_title",
105+
"company_name",
106+
"number_of_employees",
107+
"how_did_you_hear_about_us",
108+
"internal_tools",
109+
"technical_level",
110+
}
111+
112+
113+
def track_demo_form_posthog_submission(form_data: dict[str, Any]) -> rx.event.EventSpec:
114+
"""Capture a demo_request event in PostHog.
115+
116+
Returns:
117+
Event that runs PostHog identify and capture in the browser.
118+
"""
119+
return _track_form_posthog("demo_request", form_data, _COMMON_KEYS)
120+
121+
122+
def track_intro_form_posthog_submission(
123+
form_data: dict[str, Any],
124+
) -> rx.event.EventSpec:
125+
"""Capture an intro_submit event in PostHog.
126+
127+
Returns:
128+
Event that runs PostHog identify and capture in the browser.
129+
"""
130+
return _track_form_posthog(
131+
"intro_submit", form_data, _COMMON_KEYS | {"phone_number"}
132+
)
133+
134+
63135
def get_posthog_trackers(
64136
project_id: str,
65137
api_host: str = POSTHOG_API_HOST,

0 commit comments

Comments
 (0)