📈(backend) capture an event when a document is created and deleted

When a document is created and deleted, we capture an event and we send
it to posthog.
This commit is contained in:
Manuel Raynaud
2026-06-01 17:41:55 +02:00
parent 5eb9c4edd6
commit d495b158c1
6 changed files with 96 additions and 28 deletions
+3
View File
@@ -23,6 +23,7 @@ from core.services.converter_services import (
ConversionError,
Converter,
)
from core.utils.analytics import PosthogEventName, posthog_capture
from core.utils.treebeard import create_tree_node_with_retry
@@ -490,6 +491,8 @@ class ServerCreateDocumentSerializer(serializers.Serializer):
)
)
posthog_capture(PosthogEventName.DOC_CREATED, user, {}, document=document)
if user:
# Associate the document with the pre-existing user
models.DocumentAccess.objects.create(
+16
View File
@@ -67,6 +67,7 @@ from core.services.search_indexers import (
get_visited_document_ids_of,
)
from core.tasks.mail import send_ask_for_access_mail
from core.utils.analytics import PosthogEventName, posthog_capture
from core.utils.paths import filter_descendants
from core.utils.treebeard import create_tree_node_with_retry
from core.utils.users import users_sharing_documents_with
@@ -778,10 +779,18 @@ class DocumentViewSet(
role=models.RoleChoices.OWNER,
)
posthog_capture(
PosthogEventName.DOC_CREATED, self.request.user, {}, document=obj
)
def perform_destroy(self, instance):
"""Override to implement a soft delete instead of dumping the record in database."""
instance.soft_delete()
posthog_capture(
PosthogEventName.DOC_DELETED, self.request.user, {}, document=instance
)
def _can_user_edit_document(self, document_id, set_cache=False):
"""Check if the user can edit the document."""
try:
@@ -1112,6 +1121,13 @@ class DocumentViewSet(
# Set the created instance to the serializer
serializer.instance = child_document
posthog_capture(
PosthogEventName.DOC_CREATED,
self.request.user,
{"document_parent": str(document.id)},
document=child_document,
)
headers = self.get_success_headers(serializer.data)
return drf.response.Response(
serializer.data, status=status.HTTP_201_CREATED, headers=headers
@@ -4,6 +4,7 @@ Tests for Documents API endpoint in impress's core app: children create
from concurrent.futures import ThreadPoolExecutor
from io import BytesIO
from unittest import mock
from unittest.mock import patch
from uuid import uuid4
@@ -13,6 +14,7 @@ from rest_framework.test import APIClient
from core import factories
from core.models import Document, LinkReachChoices, LinkRoleChoices
from core.services import mime_types
from core.utils.analytics import PosthogEventName
pytestmark = pytest.mark.django_db
@@ -105,12 +107,13 @@ def test_api_documents_children_create_authenticated_success(reach, role, depth)
parent=document, link_reach="restricted"
)
response = client.post(
f"/api/v1.0/documents/{document.id!s}/children/",
{
"title": "my child",
},
)
with mock.patch("core.api.viewsets.posthog_capture") as mock_capture:
response = client.post(
f"/api/v1.0/documents/{document.id!s}/children/",
{
"title": "my child",
},
)
assert response.status_code == 201
@@ -120,6 +123,13 @@ def test_api_documents_children_create_authenticated_success(reach, role, depth)
# Access objects on the child are not necessary
assert child.accesses.exists() is False
mock_capture.assert_called_once_with(
PosthogEventName.DOC_CREATED,
user,
{"document_parent": str(document.id)},
document=child,
)
@pytest.mark.parametrize("depth", [1, 2, 3])
def test_api_documents_children_create_related_forbidden(depth):
@@ -3,6 +3,7 @@ Tests for Documents API endpoint in impress's core app: create
"""
from concurrent.futures import ThreadPoolExecutor
from unittest import mock
from uuid import uuid4
import pytest
@@ -10,6 +11,7 @@ from rest_framework.test import APIClient
from core import factories
from core.models import Document
from core.utils.analytics import PosthogEventName
pytestmark = pytest.mark.django_db
@@ -37,13 +39,14 @@ def test_api_documents_create_authenticated_success():
client = APIClient()
client.force_login(user)
response = client.post(
"/api/v1.0/documents/",
{
"title": "my document",
},
format="json",
)
with mock.patch("core.api.viewsets.posthog_capture") as mock_capture:
response = client.post(
"/api/v1.0/documents/",
{
"title": "my document",
},
format="json",
)
assert response.status_code == 201
document = Document.objects.get()
@@ -51,6 +54,13 @@ def test_api_documents_create_authenticated_success():
assert document.link_reach == "restricted"
assert document.accesses.filter(role="owner", user=user).exists()
mock_capture.assert_called_once_with(
PosthogEventName.DOC_CREATED,
user,
{},
document=document,
)
@pytest.mark.django_db(transaction=True)
def test_api_documents_create_document_race_condition():
@@ -5,6 +5,7 @@ Tests for Documents API endpoint in impress's core app: create
# pylint: disable=W0621
from concurrent.futures import ThreadPoolExecutor
from unittest import mock
from unittest.mock import patch
from django.core import mail
@@ -18,6 +19,7 @@ from core.api.serializers import ServerCreateDocumentSerializer
from core.models import Document, Invitation, User
from core.services import mime_types
from core.services.converter_services import ConversionError, YdocConverter
from core.utils.analytics import PosthogEventName
pytestmark = pytest.mark.django_db
@@ -183,12 +185,13 @@ def test_api_documents_create_for_owner_existing(mock_convert_md):
"email": "irrelevant@example.com", # Should be ignored since the user already exists
}
response = APIClient().post(
"/api/v1.0/documents/create-for-owner/",
data,
format="json",
HTTP_AUTHORIZATION="Bearer DummyToken",
)
with mock.patch("core.api.serializers.posthog_capture") as mock_capture:
response = APIClient().post(
"/api/v1.0/documents/create-for-owner/",
data,
format="json",
HTTP_AUTHORIZATION="Bearer DummyToken",
)
assert response.status_code == 201
@@ -204,6 +207,13 @@ def test_api_documents_create_for_owner_existing(mock_convert_md):
assert document.creator == user
assert document.accesses.filter(user=user, role="owner").exists()
mock_capture.assert_called_once_with(
PosthogEventName.DOC_CREATED,
user,
{},
document=document,
)
assert Invitation.objects.exists() is False
assert len(mail.outbox) == 1
@@ -230,12 +240,13 @@ def test_api_documents_create_for_owner_new_user(mock_convert_md):
"email": "john.doe@example.com", # Should be used to create a new user
}
response = APIClient().post(
"/api/v1.0/documents/create-for-owner/",
data,
format="json",
HTTP_AUTHORIZATION="Bearer DummyToken",
)
with mock.patch("core.api.serializers.posthog_capture") as mock_capture:
response = APIClient().post(
"/api/v1.0/documents/create-for-owner/",
data,
format="json",
HTTP_AUTHORIZATION="Bearer DummyToken",
)
assert response.status_code == 201
@@ -251,6 +262,13 @@ def test_api_documents_create_for_owner_new_user(mock_convert_md):
assert document.creator is None
assert document.accesses.exists() is False
mock_capture.assert_called_once_with(
PosthogEventName.DOC_CREATED,
None,
{},
document=document,
)
invitation = Invitation.objects.get()
assert invitation.email == "john.doe@example.com"
assert invitation.role == "owner"
@@ -2,11 +2,14 @@
Tests for Documents API endpoint in impress's core app: delete
"""
from unittest import mock
import pytest
from rest_framework.test import APIClient
from core import factories, models
from core.tests.conftest import TEAM, USER, VIA
from core.utils.analytics import PosthogEventName
pytestmark = pytest.mark.django_db
@@ -127,9 +130,10 @@ def test_api_documents_delete_authenticated_owner(via, mock_user_teams):
document=document, team="lasuite", role="owner"
)
response = client.delete(
f"/api/v1.0/documents/{document.id}/",
)
with mock.patch("core.api.viewsets.posthog_capture") as mock_capture:
response = client.delete(
f"/api/v1.0/documents/{document.id}/",
)
assert response.status_code == 204
@@ -137,3 +141,10 @@ def test_api_documents_delete_authenticated_owner(via, mock_user_teams):
assert models.Document.objects.count() == 1
assert models.Document.objects.filter(deleted_at__isnull=True).exists() is False
assert models.Document.objects.filter(deleted_at__isnull=False).count() == 1
mock_capture.assert_called_once_with(
PosthogEventName.DOC_DELETED,
user,
{},
document=document,
)