mirror of
https://github.com/suitenumerique/docs.git
synced 2026-08-17 21:25:43 +02:00
📈(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:
@@ -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
|
||||
@@ -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,
|
||||
)
|
||||
Reference in New Issue
Block a user