Added console dynamic source, console output only flag in tasks API

pull/40/head
Piero Toffanin 2016-10-31 17:09:01 -04:00
rodzic 88fa760045
commit d7a450521c
7 zmienionych plików z 96 dodań i 9 usunięć

Wyświetl plik

@ -48,6 +48,12 @@ class TaskViewSet(viewsets.ViewSet):
raise exceptions.NotFound()
return project
@staticmethod
def task_output_only(request, task):
line_num = max(0, int(request.query_params.get('line', 0)))
output = task.console_output or ""
return '\n'.join(output.split('\n')[line_num:])
def list(self, request, project_pk=None):
project = self.get_and_check_project(request, project_pk)
tasks = self.queryset.filter(project=project_pk)
@ -61,8 +67,16 @@ class TaskViewSet(viewsets.ViewSet):
task = self.queryset.get(pk=pk, project=project_pk)
except ObjectDoesNotExist:
raise exceptions.NotFound()
serializer = TaskSerializer(task)
return Response(serializer.data)
response_data = None
if request.query_params.get('output_only', '').lower() in ['true', '1']:
response_data = self.task_output_only(request, task)
else:
serializer = TaskSerializer(task)
response_data = serializer.data
return Response(response_data)
def create(self, request, project_pk=None):
project = self.get_and_check_project(request, project_pk, ('change_project', ))

Wyświetl plik

@ -84,7 +84,7 @@ def setup():
try:
scheduler.start()
scheduler.add_job(update_nodes_info, 'interval', seconds=30)
scheduler.add_job(process_pending_tasks, 'interval', seconds=5)
scheduler.add_job(process_pending_tasks, 'interval', seconds=15)
except SchedulerAlreadyRunningError:
logger.warn("Scheduler already running (this is OK while testing)")

Wyświetl plik

@ -26,6 +26,36 @@ class Console extends React.Component {
componentDidMount(){
this.checkAutoscroll();
// Dynamic source?
if (this.props.source !== undefined){
let currentLineNumber = 0;
const updateFromSource = () => {
let sourceUrl = typeof this.props.source === 'function' ?
this.props.source(currentLineNumber) :
this.props.source;
// Fetch
this.sourceRequest = $.get(sourceUrl, text => {
let lines = text.split("\n");
lines.forEach(line => this.addLine(line));
currentLineNumber += (lines.length - 1);
})
.always(() => {
if (this.props.refreshInterval !== undefined){
this.sourceTimeout = setTimeout(updateFromSource, this.props.refreshInterval);
}
});
};
updateFromSource();
}
}
componentWillUnmount(){
if (this.sourceTimeout) clearTimeout(this.sourceTimeout);
if (this.sourceRequest) this.sourceRequest.abort();
}
setRef(domNode){

Wyświetl plik

@ -11,9 +11,11 @@ class TaskListItem extends React.Component {
}
this.toggleExpanded = this.toggleExpanded.bind(this);
this.consoleOutputUrl = this.consoleOutputUrl.bind(this);
}
componentDidMount(){
}
toggleExpanded(){
@ -22,17 +24,32 @@ class TaskListItem extends React.Component {
});
}
consoleOutputUrl(line){
return `/api/projects/${this.props.data.project}/tasks/${this.props.data.id}/?output_only=true&line=${line}`;
}
render() {
let name = this.props.data.name !== null ? this.props.data.name : `Task #${this.props.data.id}`;
let expanded = "";
if (this.state.expanded){
expanded = (
<div className="row expanded-panel">
<div className="labels">
<strong>Status: </strong> Running<br/>
<div className="expanded-panel">
<div className="row">
<div className="col-md-4 no-padding">
<div className="labels">
<strong>Status: </strong> Running<br/>
</div>
</div>
<div className="col-md-8">
<Console
source={this.consoleOutputUrl}
refreshInterval={3000}
autoscroll={true}
height={200} />
</div>
</div>
<div className="row">
<button type="button" className="btn btn-primary btn-sm">
<i className="glyphicon glyphicon-remove-circle"></i>
Restart
@ -44,7 +61,8 @@ class TaskListItem extends React.Component {
Delete
</button>
</div>
);
</div>
);
}
return (

Wyświetl plik

@ -6,6 +6,10 @@
.row{
margin: 0;
padding: 4px;
.no-padding{
padding: 0;
}
}
.name{

Wyświetl plik

@ -2,7 +2,6 @@ from django.test import TestCase
from django.contrib.auth.models import User, Group
from app.models import Project
from app.boot import boot
from app import scheduler
class BootTestCase(TestCase):
'''

Wyświetl plik

@ -101,6 +101,28 @@ class TestApi(BootTestCase):
# images_count field exists
self.assertTrue(res.data["images_count"] == 0)
# Get console output
res = client.get('/api/projects/{}/tasks/{}/?output_only=true'.format(project.id, task.id))
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertTrue(res.data == "")
task.console_output = "line1\nline2\nline3"
task.save()
res = client.get('/api/projects/{}/tasks/{}/?output_only=true'.format(project.id, task.id))
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertTrue(res.data == task.console_output)
# Console output with line num
res = client.get('/api/projects/{}/tasks/{}/?output_only=true&line=2'.format(project.id, task.id))
self.assertTrue(res.data == "line3")
# Console output with line num out of bounds
res = client.get('/api/projects/{}/tasks/{}/?output_only=true&line=3'.format(project.id, task.id))
self.assertTrue(res.data == "")
res = client.get('/api/projects/{}/tasks/{}/?output_only=true&line=-1'.format(project.id, task.id))
self.assertTrue(res.data == task.console_output)
# Cannot list task details for a task belonging to a project we don't have access to
res = client.get('/api/projects/{}/tasks/{}/'.format(other_project.id, other_task.id))
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)