Skip to content

Commit 1dce09b

Browse files
committed
Builds: restart build commands before a new build
This avoids ending up with duplicate commands. This can be tested by manually triggering a retry in any part of the task. The user may still see duplicate commands, as our js always adds new commands to the end, but after a refresh the correct number of commands are shown.
1 parent 3c9dafe commit 1dce09b

File tree

5 files changed

+54
-8
lines changed

5 files changed

+54
-8
lines changed

readthedocs/api/v2/views/model_views.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,25 @@ def retrieve(self, *args, **kwargs):
272272
)
273273
return Response(data)
274274

275+
@decorators.action(
276+
detail=True,
277+
permission_classes=[permissions.IsAdminUser],
278+
methods=['post'],
279+
)
280+
def restart(self, request, **kwargs):
281+
"""
282+
Restart the build so it can be re-used when re-trying.
283+
284+
Dates and states are usually overriden by the build,
285+
we care more about deleting the commands.
286+
"""
287+
instance = self.get_object()
288+
instance.output = ''
289+
instance.cold_storage = False
290+
instance.commands.all().delete()
291+
instance.save()
292+
return Response(status=status.HTTP_204_NO_CONTENT)
293+
275294

276295
class BuildCommandViewSet(UserSelectViewSet):
277296
parser_classes = [JSONParser, MultiPartParser]

readthedocs/projects/tasks.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
from readthedocs.projects.models import APIProject, Feature
7373
from readthedocs.search.utils import index_new_files, remove_indexed_files
7474
from readthedocs.sphinx_domains.models import SphinxDomain
75-
from readthedocs.storage import build_media_storage, build_environment_storage
75+
from readthedocs.storage import build_environment_storage, build_media_storage
7676
from readthedocs.vcs_support import utils as vcs_support_utils
7777
from readthedocs.worker import app
7878

@@ -670,6 +670,8 @@ def run_setup(self, record=True):
670670
671671
Return True if successful.
672672
"""
673+
api_v2.build(self.build['id']).restart.post()
674+
673675
if settings.DOCKER_ENABLE:
674676
env_cls = DockerBuildEnvironment
675677
else:

readthedocs/rtd_tests/tests/test_api.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@
5555
class APIBuildTests(TestCase):
5656
fixtures = ['eric.json', 'test_data.json']
5757

58+
def setUp(self):
59+
self.user = User.objects.get(username='eric')
60+
self.project = get(Project, users=[self.user])
61+
self.version = self.project.versions.get(slug=LATEST)
62+
5863
def test_make_build(self):
5964
"""Test that a superuser can use the API."""
6065
client = APIClient()
@@ -82,6 +87,27 @@ def test_make_build(self):
8287
self.assertEqual(build['output'], 'Test Output')
8388
self.assertEqual(build['state_display'], 'Cloning')
8489

90+
def test_restart_build(self):
91+
build = get(
92+
Build,
93+
project=self.project,
94+
version=self.version,
95+
)
96+
command = get(
97+
BuildCommandResult,
98+
build=build,
99+
)
100+
build.commands.add(command)
101+
102+
self.assertEqual(build.commands.count(), 1)
103+
104+
client = APIClient()
105+
client.force_login(self.user)
106+
r = client.post(reverse('build-restart', args=(build.pk,)))
107+
self.assertEqual(r.status_code, 204)
108+
self.assertEqual(build.commands.count(), 0)
109+
110+
85111
def test_api_does_not_have_private_config_key_superuser(self):
86112
client = APIClient()
87113
client.login(username='super', password='test')

readthedocs/rtd_tests/tests/test_builds.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
# -*- coding: utf-8 -*-
21
import datetime
32
import os
4-
53
from unittest import mock
4+
5+
from allauth.socialaccount.models import SocialAccount
66
from django.contrib.auth.models import User
77
from django.test import TestCase
8-
from django_dynamic_fixture import fixture, get
98
from django.utils import timezone
10-
11-
from allauth.socialaccount.models import SocialAccount
9+
from django_dynamic_fixture import fixture, get
1210

1311
from readthedocs.builds.constants import (
1412
BRANCH,
1513
EXTERNAL,
14+
GENERIC_EXTERNAL_VERSION_NAME,
1615
GITHUB_EXTERNAL_VERSION_NAME,
1716
GITLAB_EXTERNAL_VERSION_NAME,
18-
GENERIC_EXTERNAL_VERSION_NAME
1917
)
2018
from readthedocs.builds.models import Build, Version
2119
from readthedocs.core.utils import trigger_build
@@ -325,7 +323,7 @@ def test_save_config_in_build_model(self, load_config, api_v2):
325323
task.run_setup()
326324
build_config = task.build['config']
327325
# For patch
328-
api_v2.build.assert_called_once()
326+
api_v2.build().patch.assert_called_once()
329327
assert build_config['version'] == '1'
330328
assert 'sphinx' in build_config
331329
assert build_config['doctype'] == 'sphinx'

readthedocs/rtd_tests/tests/test_privacy_urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@ def setUp(self):
382382
}
383383
self.response_data = {
384384
'build-concurrent': {'status_code': 403},
385+
'build-restart': {'status_code': 403},
385386
'project-sync-versions': {'status_code': 403},
386387
'project-token': {'status_code': 403},
387388
'emailhook-list': {'status_code': 403},

0 commit comments

Comments
 (0)