📈(backend) create a utils to capture event with posthog

We want to capture event with posthog. We created a utils for this
reposible to send the event or not if posthog is configured.
This commit is contained in:
Manuel Raynaud
2026-06-01 17:41:55 +02:00
parent e6a7657c89
commit 5eb9c4edd6
3 changed files with 185 additions and 0 deletions
+1
View File
@@ -14,6 +14,7 @@ and this project adheres to
- 🔧(backend) allow configuring settings OIDC_OP_USER_ENDPOINT_FORMAT
- ⚡️(helm) create a dedicated svc and deployment for yprovider converter #2368
- ✨(backend) allow to leave a document #2365
- 📈(backend) create a utils to capture event with posthog
### Changed
@@ -0,0 +1,137 @@
"""Test analytics utilities."""
import json
from unittest import mock
from django.contrib.auth.models import AnonymousUser
import pytest
from core import factories
from core.utils.analytics import PosthogEventName, posthog_capture
pytestmark = pytest.mark.django_db
def test_posthog_capture_no_posthog_key():
"""When POSTHOG_KEY is not set, posthog.capture should not be called."""
with mock.patch("core.utils.analytics.posthog.capture") as mock_capture:
posthog_capture(PosthogEventName.DOC_CREATED, None)
mock_capture.assert_not_called()
def test_posthog_capture_with_user_and_document(settings):
"""posthog.capture should be called with correct args when user and document are provided."""
settings.POSTHOG_KEY = "test-key"
user = factories.UserFactory()
document = factories.DocumentFactory()
properties = {"custom": "value"}
with mock.patch("core.utils.analytics.posthog.capture") as mock_capture:
posthog_capture(
PosthogEventName.DOC_CREATED,
user,
properties,
document=document,
)
mock_capture.assert_called_once_with(
PosthogEventName.DOC_CREATED,
distinct_id=user.email,
properties={
"custom": "value",
"document_id": str(document.id),
"document_title": document.title,
"document_depth": document.depth,
"document_path": document.path,
},
)
def test_posthog_capture_with_user_no_document(settings):
"""posthog.capture should be called with correct args when no document is provided."""
settings.POSTHOG_KEY = "test-key"
user = factories.UserFactory()
properties = {"foo": "bar"}
with mock.patch("core.utils.analytics.posthog.capture") as mock_capture:
posthog_capture(PosthogEventName.DOC_DELETED, user, properties)
mock_capture.assert_called_once_with(
PosthogEventName.DOC_DELETED,
distinct_id=user.email,
properties={"foo": "bar"},
)
def test_posthog_capture_no_user(settings):
"""When user is None, distinct_id should be None."""
settings.POSTHOG_KEY = "test-key"
with mock.patch("core.utils.analytics.posthog.capture") as mock_capture:
posthog_capture(PosthogEventName.DOC_CREATED, None)
mock_capture.assert_called_once_with(
PosthogEventName.DOC_CREATED,
distinct_id=None,
properties={},
)
def test_posthog_capture_anonymous_user(settings):
"""An anonymous user (no email attribute) should resolve to a None distinct_id."""
settings.POSTHOG_KEY = "test-key"
with mock.patch("core.utils.analytics.posthog.capture") as mock_capture:
posthog_capture(PosthogEventName.DOC_AI_ACTION, AnonymousUser())
mock_capture.assert_called_once_with(
PosthogEventName.DOC_AI_ACTION,
distinct_id=None,
properties={},
)
def test_posthog_capture_properties_are_json_serializable(settings):
"""The document properties must be JSON-serializable (document_id is a str, not a UUID)."""
settings.POSTHOG_KEY = "test-key"
user = factories.UserFactory()
document = factories.DocumentFactory()
with mock.patch("core.utils.analytics.posthog.capture") as mock_capture:
posthog_capture(PosthogEventName.DOC_CREATED, user, document=document)
called_properties = mock_capture.call_args[1]["properties"]
assert isinstance(called_properties["document_id"], str)
# Mirrors PostHog's flush: properties must serialize without raising.
json.dumps(called_properties)
def test_posthog_capture_no_properties(settings):
"""When properties is None, it should default to an empty dict."""
settings.POSTHOG_KEY = "test-key"
user = factories.UserFactory()
with mock.patch("core.utils.analytics.posthog.capture") as mock_capture:
posthog_capture(PosthogEventName.DOC_DELETED, user, None)
mock_capture.assert_called_once_with(
PosthogEventName.DOC_DELETED,
distinct_id=user.email,
properties={},
)
def test_posthog_capture_properties_not_mutated(settings):
"""The original properties dict should not be mutated."""
settings.POSTHOG_KEY = "test-key"
user = factories.UserFactory()
document = factories.DocumentFactory()
original_properties = {"key": "value"}
with mock.patch("core.utils.analytics.posthog.capture") as mock_capture:
posthog_capture(
PosthogEventName.DOC_CREATED,
user,
original_properties,
document=document,
)
assert original_properties == {"key": "value"}
# Verify the merged properties were passed to posthog
called_properties = mock_capture.call_args[1]["properties"]
assert "document_id" in called_properties
assert "document_id" not in original_properties
+47
View File
@@ -0,0 +1,47 @@
"""
PostHog utilities
"""
from enum import StrEnum
from django.conf import settings
import posthog
from core.models import User
class PosthogEventName(StrEnum):
"""Posthog event name enum"""
DOC_CREATED = "doc_created"
DOC_DELETED = "doc_deleted"
def posthog_capture(
event_name: PosthogEventName,
user: User | None,
properties: dict | None = None,
**kwargs,
):
"""Capture an event with PostHog."""
if settings.POSTHOG_KEY:
if properties is None:
properties = {}
properties = properties.copy()
document = kwargs.get("document")
if document:
properties.update(
{
"document_id": str(document.id),
"document_title": document.title,
"document_depth": document.depth,
"document_path": document.path,
}
)
posthog.capture(
event_name,
distinct_id=getattr(user, "email", None),
properties=properties,
)