Merge pull request #3585 from stsewd/download-raw-log

Download raw build log
remove-default-role
Eric Holscher 2018-05-30 17:54:22 -04:00 committed by GitHub
commit cefb86f6d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 184 additions and 3 deletions

View File

@ -102,6 +102,8 @@ class BuildSerializer(serializers.ModelSerializer):
"""Build serializer for user display, doesn't display internal fields."""
commands = BuildCommandSerializer(many=True, read_only=True)
project_slug = serializers.ReadOnlyField(source='project.slug')
version_slug = serializers.ReadOnlyField(source='version.slug')
docs_url = serializers.ReadOnlyField(source='version.get_absolute_url')
state_display = serializers.ReadOnlyField(source='get_state_display')

View File

@ -0,0 +1,14 @@
Read the Docs build information
Build id: {{ build.id }}
Project: {{ build.project_slug }}
Version: {{ build.version_slug }}
Commit: {{ build.commit }}
Date: {{ build.date }}
State: {{ build.state }}
Success: {% if build.state == 'finished' %}{{ build.success }}{% else %}Unknown{% endif %}
{% for command in build.commands %}
[rtd-command-info] start-time: {{ command.start_time }}, end-time: {{ command.end_time }}, duration: {{ command.run_time }}, exit-code: {{ command.exit_code }}
{{ command.command|safe }}
{{ command.output|safe }}
{% endfor %}

View File

@ -9,8 +9,9 @@ import logging
from allauth.socialaccount.models import SocialAccount
from django.shortcuts import get_object_or_404
from rest_framework import decorators, permissions, status, viewsets
from django.template.loader import render_to_string
from rest_framework.decorators import detail_route
from rest_framework.renderers import JSONRenderer
from rest_framework.renderers import BaseRenderer, JSONRenderer
from rest_framework.response import Response
from readthedocs.builds.constants import BRANCH, TAG
@ -34,6 +35,28 @@ from ..serializers import (
log = logging.getLogger(__name__)
class PlainTextBuildRenderer(BaseRenderer):
"""
Custom renderer for text/plain format.
charset is 'utf-8' by default.
"""
media_type = 'text/plain'
format = 'txt'
def render(self, data, accepted_media_type=None, renderer_context=None):
renderer_context = renderer_context or {}
response = renderer_context.get('response')
if not response or response.exception:
return data.get('detail', '').encode(self.charset)
data = render_to_string(
'restapi/log.txt', {'build': data}
)
return data.encode(self.charset)
class UserSelectViewSet(viewsets.ModelViewSet):
"""
@ -213,7 +236,7 @@ class VersionViewSet(UserSelectViewSet):
class BuildViewSetBase(UserSelectViewSet):
permission_classes = [APIRestrictedPermission]
renderer_classes = (JSONRenderer,)
renderer_classes = (JSONRenderer, PlainTextBuildRenderer)
serializer_class = BuildSerializer
admin_serializer_class = BuildAdminSerializer
model = Build

View File

@ -17,7 +17,7 @@ from django_dynamic_fixture import get
from rest_framework import status
from rest_framework.test import APIClient
from readthedocs.builds.models import Build, Version
from readthedocs.builds.models import Build, BuildCommandResult, Version
from readthedocs.integrations.models import Integration
from readthedocs.oauth.models import RemoteOrganization, RemoteRepository
from readthedocs.projects.models import Feature, Project
@ -277,6 +277,140 @@ class APIBuildTests(TestCase):
self.assertEqual(build['commands'][0]['run_time'], 5)
self.assertEqual(build['commands'][0]['description'], 'foo')
def test_get_raw_log_success(self):
build = get(Build, project_id=1, version_id=1, builder='foo')
get(
BuildCommandResult,
build=build,
command='python setup.py install',
output='Installing dependencies...'
)
get(
BuildCommandResult,
build=build,
command='git checkout master',
output='Switched to branch "master"'
)
client = APIClient()
api_user = get(User, user='test', password='test')
client.force_authenticate(user=api_user)
resp = client.get('/api/v2/build/{0}.txt'.format(build.pk))
self.assertEqual(resp.status_code, 200)
self.assertIn('Read the Docs build information', resp.content.decode())
self.assertIn('Build id: {}'.format(build.id), resp.content.decode())
self.assertIn('Project: {}'.format(build.project.slug), resp.content.decode())
self.assertIn('Version: {}'.format(build.version.slug), resp.content.decode())
self.assertIn('Commit: {}'.format(build.commit), resp.content.decode())
self.assertIn('Date: ', resp.content.decode())
self.assertIn('State: finished', resp.content.decode())
self.assertIn('Success: True', resp.content.decode())
self.assertIn('[rtd-command-info]', resp.content.decode())
self.assertIn(
'python setup.py install\nInstalling dependencies...',
resp.content.decode()
)
self.assertIn(
'git checkout master\nSwitched to branch "master"',
resp.content.decode()
)
def test_get_raw_log_building(self):
build = get(
Build, project_id=1, version_id=1,
builder='foo', success=False,
exit_code=1, state='building',
)
get(
BuildCommandResult,
build=build,
command='python setup.py install',
output='Installing dependencies...',
exit_code=1,
)
get(
BuildCommandResult,
build=build,
command='git checkout master',
output='Switched to branch "master"'
)
client = APIClient()
api_user = get(User, user='test', password='test')
client.force_authenticate(user=api_user)
resp = client.get('/api/v2/build/{0}.txt'.format(build.pk))
self.assertEqual(resp.status_code, 200)
self.assertIn('Read the Docs build information', resp.content.decode())
self.assertIn('Build id: {}'.format(build.id), resp.content.decode())
self.assertIn('Project: {}'.format(build.project.slug), resp.content.decode())
self.assertIn('Version: {}'.format(build.version.slug), resp.content.decode())
self.assertIn('Commit: {}'.format(build.commit), resp.content.decode())
self.assertIn('Date: ', resp.content.decode())
self.assertIn('State: building', resp.content.decode())
self.assertIn('Success: Unknow', resp.content.decode())
self.assertIn('[rtd-command-info]', resp.content.decode())
self.assertIn(
'python setup.py install\nInstalling dependencies...',
resp.content.decode()
)
self.assertIn(
'git checkout master\nSwitched to branch "master"',
resp.content.decode()
)
def test_get_raw_log_failure(self):
build = get(
Build, project_id=1, version_id=1,
builder='foo', success=False, exit_code=1
)
get(
BuildCommandResult,
build=build,
command='python setup.py install',
output='Installing dependencies...',
exit_code=1,
)
get(
BuildCommandResult,
build=build,
command='git checkout master',
output='Switched to branch "master"'
)
client = APIClient()
api_user = get(User, user='test', password='test')
client.force_authenticate(user=api_user)
resp = client.get('/api/v2/build/{0}.txt'.format(build.pk))
self.assertEqual(resp.status_code, 200)
self.assertIn('Read the Docs build information', resp.content.decode())
self.assertIn('Build id: {}'.format(build.id), resp.content.decode())
self.assertIn('Project: {}'.format(build.project.slug), resp.content.decode())
self.assertIn('Version: {}'.format(build.version.slug), resp.content.decode())
self.assertIn('Commit: {}'.format(build.commit), resp.content.decode())
self.assertIn('Date: ', resp.content.decode())
self.assertIn('State: finished', resp.content.decode())
self.assertIn('Success: False', resp.content.decode())
self.assertIn('[rtd-command-info]', resp.content.decode())
self.assertIn(
'python setup.py install\nInstalling dependencies...',
resp.content.decode()
)
self.assertIn(
'git checkout master\nSwitched to branch "master"',
resp.content.decode()
)
def test_get_invalid_raw_log(self):
client = APIClient()
api_user = get(User, user='test', password='test')
client.force_authenticate(user=api_user)
resp = client.get('/api/v2/build/{0}.txt'.format(404))
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)
class APITests(TestCase):
fixtures = ['eric.json', 'test_data.json']

View File

@ -61,6 +61,14 @@ $(document).ready(function () {
</a>
</li>
</div>
<div data-bind="visible: finished()">
<li>
<a href="{% url "build-detail" build.pk "txt" %}">
{% trans "View raw" %}
</a>
</li>
</div>
</ul>
<div class="build-id">