mirror of
https://github.com/ansible/awx.git
synced 2024-11-01 08:21:15 +03:00
Merge pull request #2065 from jakemcdermott/2058
add host_status, play, and task counts to job details
This commit is contained in:
commit
b1f36572c6
@ -9,7 +9,7 @@ import operator
|
||||
import re
|
||||
import six
|
||||
import urllib
|
||||
from collections import OrderedDict
|
||||
from collections import defaultdict, OrderedDict
|
||||
from datetime import timedelta
|
||||
|
||||
# OAuth2
|
||||
@ -3144,6 +3144,48 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer):
|
||||
return summary_fields
|
||||
|
||||
|
||||
class JobDetailSerializer(JobSerializer):
|
||||
|
||||
host_status_counts = serializers.SerializerMethodField(
|
||||
help_text=_('A count of hosts uniquely assigned to each status.'),
|
||||
)
|
||||
playbook_counts = serializers.SerializerMethodField(
|
||||
help_text=_('A count of all plays and tasks for the job run.'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Job
|
||||
fields = ('*', 'host_status_counts', 'playbook_counts',)
|
||||
|
||||
def get_playbook_counts(self, obj):
|
||||
task_count = obj.job_events.filter(event='playbook_on_task_start').count()
|
||||
play_count = obj.job_events.filter(event='playbook_on_play_start').count()
|
||||
|
||||
data = {'play_count': play_count, 'task_count': task_count}
|
||||
|
||||
return data
|
||||
|
||||
def get_host_status_counts(self, obj):
|
||||
try:
|
||||
event_data = obj.job_events.only('event_data').get(event='playbook_on_stats').event_data
|
||||
except JobEvent.DoesNotExist:
|
||||
event_data = {}
|
||||
|
||||
host_status = {}
|
||||
host_status_keys = ['skipped', 'ok', 'changed', 'failures', 'dark']
|
||||
|
||||
for key in host_status_keys:
|
||||
for host in event_data.get(key, {}):
|
||||
host_status[host] = key
|
||||
|
||||
host_status_counts = defaultdict(lambda: 0)
|
||||
|
||||
for value in host_status.values():
|
||||
host_status_counts[value] += 1
|
||||
|
||||
return host_status_counts
|
||||
|
||||
|
||||
class JobCancelSerializer(BaseSerializer):
|
||||
|
||||
can_cancel = serializers.BooleanField(read_only=True)
|
||||
|
@ -4080,7 +4080,7 @@ class JobDetail(UnifiedJobDeletionMixin, RetrieveUpdateDestroyAPIView):
|
||||
|
||||
model = Job
|
||||
metadata_class = JobTypeMetadata
|
||||
serializer_class = JobSerializer
|
||||
serializer_class = JobDetailSerializer
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
obj = self.get_object()
|
||||
|
@ -1,4 +1,5 @@
|
||||
# Python
|
||||
from collections import namedtuple
|
||||
import pytest
|
||||
import mock
|
||||
import json
|
||||
@ -7,6 +8,7 @@ from six.moves import xrange
|
||||
|
||||
# AWX
|
||||
from awx.api.serializers import (
|
||||
JobDetailSerializer,
|
||||
JobSerializer,
|
||||
JobOptionsSerializer,
|
||||
)
|
||||
@ -14,6 +16,7 @@ from awx.api.serializers import (
|
||||
from awx.main.models import (
|
||||
Label,
|
||||
Job,
|
||||
JobEvent,
|
||||
)
|
||||
|
||||
|
||||
@ -53,6 +56,7 @@ def jobs(mocker):
|
||||
@mock.patch('awx.api.serializers.UnifiedJobTemplateSerializer.get_related', lambda x,y: {})
|
||||
@mock.patch('awx.api.serializers.JobOptionsSerializer.get_related', lambda x,y: {})
|
||||
class TestJobSerializerGetRelated():
|
||||
|
||||
@pytest.mark.parametrize("related_resource_name", [
|
||||
'job_events',
|
||||
'relaunch',
|
||||
@ -76,6 +80,7 @@ class TestJobSerializerGetRelated():
|
||||
@mock.patch('awx.api.serializers.BaseSerializer.to_representation', lambda self,obj: {
|
||||
'extra_vars': obj.extra_vars})
|
||||
class TestJobSerializerSubstitution():
|
||||
|
||||
def test_survey_password_hide(self, mocker):
|
||||
job = mocker.MagicMock(**{
|
||||
'display_extra_vars.return_value': '{\"secret_key\": \"$encrypted$\"}',
|
||||
@ -90,6 +95,7 @@ class TestJobSerializerSubstitution():
|
||||
|
||||
@mock.patch('awx.api.serializers.BaseSerializer.get_summary_fields', lambda x,y: {})
|
||||
class TestJobOptionsSerializerGetSummaryFields():
|
||||
|
||||
def test__summary_field_labels_10_max(self, mocker, job_template, labels):
|
||||
job_template.labels.all = mocker.MagicMock(**{'return_value': labels})
|
||||
|
||||
@ -101,3 +107,45 @@ class TestJobOptionsSerializerGetSummaryFields():
|
||||
|
||||
def test_labels_exists(self, test_get_summary_fields, job_template):
|
||||
test_get_summary_fields(JobOptionsSerializer, job_template, 'labels')
|
||||
|
||||
|
||||
class TestJobDetailSerializerGetHostStatusCountFields(object):
|
||||
|
||||
def test_hosts_are_counted_once(self, job, mocker):
|
||||
mock_event = JobEvent(**{
|
||||
'event': 'playbook_on_stats',
|
||||
'event_data': {
|
||||
'skipped': {
|
||||
'localhost': 2,
|
||||
'fiz': 1,
|
||||
},
|
||||
'ok': {
|
||||
'localhost': 1,
|
||||
'foo': 2,
|
||||
},
|
||||
'changed': {
|
||||
'localhost': 1,
|
||||
'bar': 3,
|
||||
},
|
||||
'dark': {
|
||||
'localhost': 2,
|
||||
'fiz': 2,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
mock_qs = namedtuple('mock_qs', ['get'])(mocker.MagicMock(return_value=mock_event))
|
||||
job.job_events.only = mocker.MagicMock(return_value=mock_qs)
|
||||
|
||||
serializer = JobDetailSerializer()
|
||||
host_status_counts = serializer.get_host_status_counts(job)
|
||||
|
||||
assert host_status_counts == {'ok': 1, 'changed': 1, 'dark': 2}
|
||||
|
||||
def test_host_status_counts_is_empty_dict_without_stats_event(self, job, mocker):
|
||||
job.job_events = JobEvent.objects.none()
|
||||
|
||||
serializer = JobDetailSerializer()
|
||||
host_status_counts = serializer.get_host_status_counts(job)
|
||||
|
||||
assert host_status_counts == {}
|
||||
|
Loading…
Reference in New Issue
Block a user