Skip to content

Commit 64b97df

Browse files
authored
Merge pull request #1226 from thunderstore-io/feature/TS-2985/kafka-profile-export-event
Profile Export Events to Kafka
2 parents 26f1d37 + c3b0c64 commit 64b97df

File tree

3 files changed

+73
-2
lines changed

3 files changed

+73
-2
lines changed

django/thunderstore/modpacks/api/experimental/tests/test_legacyprofile.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import io
22
import json
3+
from unittest.mock import patch
34

45
import pytest
56
import requests
67
from django.core.files import File
8+
from django.db import connection
79
from django.urls import reverse
810
from rest_framework.test import APIClient
911

1012
from thunderstore.modpacks.models import LegacyProfile
1113
from thunderstore.modpacks.models.legacyprofile import LEGACYPROFILE_STORAGE_CAP
14+
from thunderstore.ts_analytics.kafka import KafkaTopic
1215

1316

1417
@pytest.mark.django_db
@@ -107,3 +110,41 @@ def test_experimental_api_legacyprofile_retrieve(api_client: APIClient) -> None:
107110
response = requests.get(response["Location"])
108111
assert response.status_code == 200
109112
assert response.content == test_content
113+
114+
115+
@pytest.mark.django_db(transaction=True)
116+
def test_experimental_api_legacyprofile_create_sends_kafka_event(
117+
api_client: APIClient,
118+
) -> None:
119+
with patch(
120+
"thunderstore.modpacks.api.experimental.views.legacyprofile.send_kafka_message"
121+
) as mock_send_kafka_message:
122+
assert LegacyProfile.objects.count() == 0
123+
test_content = b"test profile data"
124+
125+
response = api_client.post(
126+
reverse("api:experimental:legacyprofile.create"),
127+
data=test_content,
128+
content_type="application/octet-stream",
129+
)
130+
131+
assert response.status_code == 200
132+
133+
while connection.run_on_commit:
134+
sids, func = connection.run_on_commit.pop(0)
135+
func()
136+
137+
result = response.json()
138+
profile_key = result["key"]
139+
140+
mock_send_kafka_message.delay.assert_called_once()
141+
call_args = mock_send_kafka_message.delay.call_args
142+
143+
assert call_args.kwargs["topic"] == KafkaTopic.A_LEGACY_PROFILE_EXPORT_V1
144+
145+
payload_string = call_args.kwargs["payload_string"]
146+
payload = json.loads(payload_string)
147+
148+
assert payload["id"] == profile_key
149+
assert "timestamp" in payload
150+
assert payload["file_size_bytes"] == len(test_content)

django/thunderstore/modpacks/api/experimental/views/legacyprofile.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import logging
2+
from datetime import datetime
3+
4+
from django.db import transaction
15
from django.shortcuts import redirect
26
from django.utils import timezone
37
from drf_yasg.openapi import TYPE_FILE, Schema
48
from drf_yasg.utils import swagger_auto_schema
9+
from pydantic import BaseModel
510
from rest_framework import serializers, status
611
from rest_framework.exceptions import NotFound, ValidationError
712
from rest_framework.parsers import FileUploadParser
@@ -11,6 +16,10 @@
1116

1217
from thunderstore.core.utils import replace_cdn
1318
from thunderstore.modpacks.models import LegacyProfile
19+
from thunderstore.ts_analytics.kafka import KafkaTopic
20+
from thunderstore.ts_analytics.tasks import send_kafka_message
21+
22+
logger = logging.getLogger(__name__)
1423

1524

1625
class LegacyProfileCreateResponseSerializer(serializers.Serializer):
@@ -29,6 +38,12 @@ def get_rate(self) -> str:
2938
return "6/m"
3039

3140

41+
class AnalyticsEventLegacyProfileExport(BaseModel):
42+
id: str
43+
timestamp: datetime
44+
file_size_bytes: int
45+
46+
3247
class LegacyProfileCreateApiView(APIView):
3348
permission_classes = []
3449
parser_classes = [LegacyProfileFileUploadParser]
@@ -43,9 +58,23 @@ class LegacyProfileCreateApiView(APIView):
4358
def post(self, request, *args, **kwargs):
4459
if "file" not in self.request.data or not self.request.data["file"]:
4560
raise ValidationError(detail="Request body was empty")
46-
key = LegacyProfile.objects.get_or_create_from_upload(
47-
content=self.request.data["file"]
61+
62+
file_obj = self.request.data["file"]
63+
file_size = file_obj.size
64+
65+
key = LegacyProfile.objects.get_or_create_from_upload(content=file_obj)
66+
67+
transaction.on_commit(
68+
lambda: send_kafka_message.delay(
69+
topic=KafkaTopic.A_LEGACY_PROFILE_EXPORT_V1,
70+
payload_string=AnalyticsEventLegacyProfileExport(
71+
id=str(key),
72+
timestamp=timezone.now(),
73+
file_size_bytes=file_size,
74+
).json(),
75+
)
4876
)
77+
4978
serializer = LegacyProfileCreateResponseSerializer({"key": key})
5079
return Response(
5180
serializer.data,

django/thunderstore/ts_analytics/kafka.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class KafkaTopic(str, Enum):
1717
# 'analytics' prefixed events are events that aren't necessarily bound to
1818
# any specific data structure
1919
A_PACKAGE_DOWNLOAD_V1 = "analytics.package_download.v1"
20+
A_LEGACY_PROFILE_EXPORT_V1 = "analytics.legacy_profile_export.v1"
2021

2122
# 'model' prefixed events are generally events that are explicitly bound to
2223
# models. Use Django's db table naming convention.

0 commit comments

Comments
 (0)