Skip to content

Commit 507c20b

Browse files
committed
Save build data using a task instead of using the API
1 parent e8dea92 commit 507c20b

File tree

6 files changed

+65
-54
lines changed

6 files changed

+65
-54
lines changed

readthedocs/api/v2/views/model_views.py

-12
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from readthedocs.oauth.services import GitHubService, registry
2020
from readthedocs.projects.models import Domain, Project
2121
from readthedocs.storage import build_commands_storage
22-
from readthedocs.telemetry.models import BuildData
2322

2423
from ..permissions import APIPermission, APIRestrictedPermission, IsOwner
2524
from ..serializers import (
@@ -286,17 +285,6 @@ def reset(self, request, **kwargs):
286285
instance.reset()
287286
return Response(status=status.HTTP_204_NO_CONTENT)
288287

289-
@decorators.action(
290-
detail=True,
291-
permission_classes=[permissions.IsAdminUser],
292-
methods=["post"],
293-
)
294-
def telemetry(self, request, **kwargs):
295-
"""Collect telemetry data from the build."""
296-
build = self.get_object()
297-
BuildData.objects.collect(build, request.data)
298-
return Response(status=status.HTTP_204_NO_CONTENT)
299-
300288

301289
class BuildCommandViewSet(DisableListEndpoint, UserSelectViewSet):
302290
parser_classes = [JSONParser, MultiPartParser]

readthedocs/projects/tasks/builds.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
)
4848
from readthedocs.storage import build_media_storage
4949
from readthedocs.telemetry.collectors import BuildDataCollector
50+
from readthedocs.telemetry.tasks import save_build_data
5051
from readthedocs.worker import app
5152

5253
from ..exceptions import (
@@ -543,7 +544,7 @@ def after_return(self, status, retval, task_id, args, kwargs, einfo):
543544
self.data.build['length'] = (timezone.now() - self.data.start_time).seconds
544545

545546
self.update_build(BUILD_STATE_FINISHED)
546-
self.upload_build_data()
547+
self.save_build_data()
547548

548549
build_complete.send(sender=Build, build=self.data.build)
549550

@@ -624,18 +625,21 @@ def collect_build_data(self):
624625
except Exception:
625626
log.exception("Error while collecting build data")
626627

627-
def upload_build_data(self):
628+
def save_build_data(self):
628629
"""
629-
Upload data collected from the build after the build has ended.
630+
Save the data collected from the build after it has ended.
630631
631632
This must be called after the build has finished updating its state,
632633
otherwise some attributes like ``length`` won't be available.
633634
"""
634635
try:
635636
if self.data.build_data:
636-
api_v2.build(self.data.build_pk).telemetry.post(self.data.build_data)
637+
save_build_data.delay(
638+
build_id=self.data.build_pk,
639+
data=self.data.build_data,
640+
)
637641
except Exception:
638-
log.exception("Error while uploading build data")
642+
log.exception("Error while saving build data")
639643

640644
@staticmethod
641645
def get_project(project_pk):

readthedocs/projects/tests/test_build_tasks.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from readthedocs.projects.exceptions import RepositoryError
1818
from readthedocs.projects.models import EnvironmentVariable, Project, WebHookEvent
1919
from readthedocs.projects.tasks.builds import sync_repository_task, update_docs_task
20+
from readthedocs.telemetry.models import BuildData
2021

2122
from .mockers import BuildEnvironmentMocker
2223

@@ -258,6 +259,8 @@ def test_successful_build(
258259
}
259260
)
260261

262+
assert not BuildData.objects.all().exists()
263+
261264
self._trigger_update_docs_task()
262265

263266
# It has to be called twice, ``before_start`` and ``after_return``
@@ -393,8 +396,7 @@ def test_successful_build(
393396
"error": "",
394397
}
395398

396-
request = self.requests_mock.request_history[10]
397-
assert request.path == "/api/v2/build/1/telemetry/"
399+
assert BuildData.objects.all().exists()
398400

399401
self.mocker.mocks["build_media_storage"].sync_directory.assert_has_calls(
400402
[
@@ -410,7 +412,6 @@ def test_successful_build(
410412

411413
@mock.patch("readthedocs.projects.tasks.builds.build_complete")
412414
@mock.patch("readthedocs.projects.tasks.builds.send_external_build_status")
413-
@mock.patch("readthedocs.projects.tasks.builds.UpdateDocsTask.upload_build_data")
414415
@mock.patch("readthedocs.projects.tasks.builds.UpdateDocsTask.execute")
415416
@mock.patch("readthedocs.projects.tasks.builds.UpdateDocsTask.send_notifications")
416417
@mock.patch("readthedocs.projects.tasks.builds.clean_build")
@@ -419,10 +420,11 @@ def test_failed_build(
419420
clean_build,
420421
send_notifications,
421422
execute,
422-
upload_build_data,
423423
send_external_build_status,
424424
build_complete,
425425
):
426+
assert not BuildData.objects.all().exists()
427+
426428
# Force an exception from the execution of the task. We don't really
427429
# care "where" it was raised: setup, build, syncing directories, etc
428430
execute.side_effect = Exception('Force and exception here.')
@@ -455,8 +457,8 @@ def test_failed_build(
455457
)
456458

457459
# The build data is None (we are failing the build before the environment is created)
458-
# and the API won't be hit, but we can test that the method was called at least.
459-
upload_build_data.assert_called_once()
460+
# and the task won't be run.
461+
assert not BuildData.objects.all().exists()
460462

461463
# Test we are updating the DB by calling the API with the updated build object
462464
api_request = self.requests_mock.request_history[

readthedocs/rtd_tests/tests/test_privacy_urls.py

+35-31
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@
1818
from readthedocs.core.utils.tasks import TaskNoPermission
1919
from readthedocs.integrations.models import HttpExchange, Integration
2020
from readthedocs.oauth.models import RemoteOrganization, RemoteRepository
21-
from readthedocs.projects.models import Domain, EnvironmentVariable, Project, WebHook
21+
from readthedocs.projects.models import (
22+
Domain,
23+
EnvironmentVariable,
24+
Project,
25+
WebHook,
26+
)
2227
from readthedocs.rtd_tests.utils import create_user
2328

2429

@@ -394,36 +399,35 @@ def setUp(self):
394399
'api_webhook_stripe': {},
395400
}
396401
self.response_data = {
397-
"domain-list": {"status_code": 410},
398-
"buildcommandresult-list": {"status_code": 410},
399-
"build-concurrent": {"status_code": 403},
400-
"build-telemetry": {"status_code": 403},
401-
"build-list": {"status_code": 410},
402-
"build-reset": {"status_code": 403},
403-
"project-sync-versions": {"status_code": 403},
404-
"project-token": {"status_code": 403},
405-
"emailhook-list": {"status_code": 403},
406-
"emailhook-detail": {"status_code": 403},
407-
"embed": {"status_code": 400},
408-
"docurl": {"status_code": 400},
409-
"cname": {"status_code": 400},
410-
"index_search": {"status_code": 403},
411-
"api_search": {"status_code": 400},
412-
"api_project_search": {"status_code": 400},
413-
"api_section_search": {"status_code": 400},
414-
"api_sync_remote_repositories": {"status_code": 403},
415-
"api_webhook": {"status_code": 405},
416-
"api_webhook_github": {"status_code": 405},
417-
"api_webhook_gitlab": {"status_code": 405},
418-
"api_webhook_bitbucket": {"status_code": 405},
419-
"api_webhook_generic": {"status_code": 403},
420-
"api_webhook_stripe": {"status_code": 405},
421-
"sphinxdomain-detail": {"status_code": 404},
422-
"project-list": {"status_code": 410},
423-
"remoteorganization-detail": {"status_code": 404},
424-
"remoterepository-detail": {"status_code": 404},
425-
"remoteaccount-detail": {"status_code": 404},
426-
"version-list": {"status_code": 410},
402+
'domain-list': {'status_code': 410},
403+
'buildcommandresult-list': {'status_code': 410},
404+
'build-concurrent': {'status_code': 403},
405+
'build-list': {'status_code': 410},
406+
'build-reset': {'status_code': 403},
407+
'project-sync-versions': {'status_code': 403},
408+
'project-token': {'status_code': 403},
409+
'emailhook-list': {'status_code': 403},
410+
'emailhook-detail': {'status_code': 403},
411+
'embed': {'status_code': 400},
412+
'docurl': {'status_code': 400},
413+
'cname': {'status_code': 400},
414+
'index_search': {'status_code': 403},
415+
'api_search': {'status_code': 400},
416+
'api_project_search': {'status_code': 400},
417+
'api_section_search': {'status_code': 400},
418+
'api_sync_remote_repositories': {'status_code': 403},
419+
'api_webhook': {'status_code': 405},
420+
'api_webhook_github': {'status_code': 405},
421+
'api_webhook_gitlab': {'status_code': 405},
422+
'api_webhook_bitbucket': {'status_code': 405},
423+
'api_webhook_generic': {'status_code': 403},
424+
'api_webhook_stripe': {'status_code': 405},
425+
'sphinxdomain-detail': {'status_code': 404},
426+
'project-list': {'status_code': 410},
427+
'remoteorganization-detail': {'status_code': 404},
428+
'remoterepository-detail': {'status_code': 404},
429+
'remoteaccount-detail': {'status_code': 404},
430+
'version-list': {'status_code': 410},
427431
}
428432

429433

readthedocs/telemetry/apps.py

+3
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@
1010
class TelemetryConfig(AppConfig):
1111
default_auto_field = "django.db.models.BigAutoField"
1212
name = "readthedocs.telemetry"
13+
14+
def ready(self):
15+
import readthedocs.telemetry.tasks # noqa

readthedocs/telemetry/tasks.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from readthedocs.builds.models import Build
2+
from readthedocs.telemetry.models import BuildData
3+
from readthedocs.worker import app
4+
5+
6+
@app.task(queue="web")
7+
def save_build_data(build_id, data):
8+
build = Build.objects.filter(id=build_id).first()
9+
if build:
10+
BuildData.objects.collect(build, data)

0 commit comments

Comments
 (0)