Skip to content

Show current executing command in build report #5019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.16 on 2018-12-19 21:49
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('builds', '0006_add_config_field'),
]

operations = [
migrations.AlterField(
model_name='buildcommandresult',
name='end_time',
field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='End time'),
),
migrations.AlterField(
model_name='buildcommandresult',
name='exit_code',
field=models.IntegerField(blank=True, default=None, null=True, verbose_name='Command exit code'),
),
]
9 changes: 7 additions & 2 deletions readthedocs/builds/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -606,10 +606,14 @@ class BuildCommandResult(BuildCommandResultMixin, models.Model):
command = models.TextField(_('Command'))
description = models.TextField(_('Description'), blank=True)
output = models.TextField(_('Command output'), blank=True)
exit_code = models.IntegerField(_('Command exit code'))
exit_code = models.IntegerField(
_('Command exit code'), null=True, blank=True, default=None
)

start_time = models.DateTimeField(_('Start time'))
end_time = models.DateTimeField(_('End time'))
end_time = models.DateTimeField(
_('End time'), null=True, blank=True, default=None
)

class Meta(object):
ordering = ['start_time']
Expand All @@ -628,3 +632,4 @@ def run_time(self):
if self.start_time is not None and self.end_time is not None:
diff = self.end_time - self.start_time
return diff.seconds
return None
12 changes: 10 additions & 2 deletions readthedocs/builds/static-src/builds/js/detail.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ function BuildCommand(data) {
self.id = ko.observable(data.id);
self.command = ko.observable(data.command);
self.output = ko.observable(data.output);
self.exit_code = ko.observable(data.exit_code || 0);
self.successful = ko.observable(self.exit_code() === 0);
self.exit_code = ko.observable(data.exit_code);
self.successful = ko.observable(self.exit_code() === null || self.exit_code() === 0);
self.run_time = ko.observable(data.run_time);
self.is_showing = ko.observable(!self.successful());

Expand All @@ -23,6 +23,10 @@ function BuildCommand(data) {
'build-command-successful' :
'build-command-failed';
});

self.is_running = ko.computed(function () {
return self.exit_code() !== null && self.run_time() != null;
});
}

function BuildDetailView(instance) {
Expand Down Expand Up @@ -83,6 +87,10 @@ function BuildDetailView(instance) {
);
if (!match) {
self.commands.push(command);
} else {
match.output = command.output;
match.exit_code = command.exit_code;
match.run_time = command.run_time;
}
}
});
Expand Down
29 changes: 23 additions & 6 deletions readthedocs/doc_builder/environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ class BuildCommand(BuildCommandResultMixin):
def __init__(self, command, cwd=None, shell=False, environment=None,
combine_output=True, input_data=None, build_env=None,
bin_path=None, description=None, record_as_success=False):
self.command_id = None
self.command = command
self.shell = shell
if cwd is None:
Expand Down Expand Up @@ -249,6 +250,18 @@ def get_command(self):

def save(self):
"""Save this command and result via the API."""
self.start_time = self.start_time or datetime.utcnow()
data = {
'build': self.build_env.build.get('id'),
'command': self.get_command(),
'description': self.description,
'start_time': self.start_time,
}
resp = api_v2.command.post(data)
self.command_id = resp['id']

def update(self):
"""Update this command and result via the API."""
# Force record this command as success to avoid Build reporting errors
# on commands that are just for checking purposes and do not interferes
# in the Build
Expand All @@ -257,15 +270,11 @@ def save(self):
self.exit_code = 0

data = {
'build': self.build_env.build.get('id'),
'command': self.get_command(),
'description': self.description,
'output': self.output,
'exit_code': self.exit_code,
'start_time': self.start_time,
'end_time': self.end_time,
}
api_v2.command.post(data)
api_v2.command(self.command_id).patch(data)


class DockerBuildCommand(BuildCommand):
Expand Down Expand Up @@ -363,6 +372,9 @@ def __init__(self, project, environment=None):
def record_command(self, command):
pass

def update_command(self, command):
pass

def run(self, *cmd, **kwargs):
"""Shortcut to run command from environment."""
return self.run_command_class(cls=self.command_class, cmd=cmd, **kwargs)
Expand Down Expand Up @@ -404,13 +416,15 @@ def run_command_class(
# ``build_env`` is passed as ``kwargs`` when it's called from a
# ``*BuildEnvironment``
build_cmd = cls(cmd, **kwargs)
if record:
self.record_command(build_cmd)
build_cmd.run()

if record:
# TODO: I don't like how it's handled this entry point here since
# this class should know nothing about a BuildCommand (which are the
# only ones that can be saved/recorded)
self.record_command(build_cmd)
self.update_command(build_cmd)

# We want append this command to the list of commands only if it has
# to be recorded in the database (to keep consistency) and also, it
Expand Down Expand Up @@ -559,6 +573,9 @@ def handle_exception(self, exc_type, exc_value, _):
def record_command(self, command):
command.save()

def update_command(self, command):
command.update()

def run(self, *cmd, **kwargs):
kwargs.update({
'build_env': self,
Expand Down
10 changes: 10 additions & 0 deletions readthedocs/rtd_tests/tests/test_builds.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ def test_build(self, load_config):
self.mocks.configure_mock('api', {
'get.return_value': {'downloads': "no_url_here"}
})
self.mocks.configure_mock('api_versions', {'return_value': [version]})
self.mocks.configure_mock(
'api_v2.command', {'post.return_value': {'id': 1}}
)
self.mocks.patches['html_build'].stop()

build_env = LocalBuildEnvironment(project=project, version=version, build={})
Expand Down Expand Up @@ -188,6 +192,9 @@ def test_build_pdf_latex_failures(self, load_config):
load_config.side_effect = create_load()
self.mocks.patches['html_build'].stop()
self.mocks.patches['pdf_build'].stop()
self.mocks.configure_mock(
'api_v2.command', {'post.return_value': {'id': 1}}
)

project = get(Project,
slug='project-1',
Expand Down Expand Up @@ -234,6 +241,9 @@ def test_build_pdf_latex_not_failure(self, load_config):
load_config.side_effect = create_load()
self.mocks.patches['html_build'].stop()
self.mocks.patches['pdf_build'].stop()
self.mocks.configure_mock(
'api_v2.command', {'post.return_value': {'id': 1}}
)

project = get(Project,
slug='project-2',
Expand Down
Loading