mirror of
https://github.com/ansible/awx.git
synced 2026-02-19 12:10:06 -03:30
add host_status, play, and task counts to job details
This commit is contained in:
@@ -9,7 +9,7 @@ import operator
|
|||||||
import re
|
import re
|
||||||
import six
|
import six
|
||||||
import urllib
|
import urllib
|
||||||
from collections import OrderedDict
|
from collections import defaultdict, OrderedDict
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
# OAuth2
|
# OAuth2
|
||||||
@@ -3130,6 +3130,48 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer):
|
|||||||
return summary_fields
|
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):
|
class JobCancelSerializer(BaseSerializer):
|
||||||
|
|
||||||
can_cancel = serializers.BooleanField(read_only=True)
|
can_cancel = serializers.BooleanField(read_only=True)
|
||||||
|
|||||||
@@ -4080,7 +4080,7 @@ class JobDetail(UnifiedJobDeletionMixin, RetrieveUpdateDestroyAPIView):
|
|||||||
|
|
||||||
model = Job
|
model = Job
|
||||||
metadata_class = JobTypeMetadata
|
metadata_class = JobTypeMetadata
|
||||||
serializer_class = JobSerializer
|
serializer_class = JobDetailSerializer
|
||||||
|
|
||||||
def update(self, request, *args, **kwargs):
|
def update(self, request, *args, **kwargs):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# Python
|
# Python
|
||||||
|
from collections import namedtuple
|
||||||
import pytest
|
import pytest
|
||||||
import mock
|
import mock
|
||||||
import json
|
import json
|
||||||
@@ -7,6 +8,7 @@ from six.moves import xrange
|
|||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.api.serializers import (
|
from awx.api.serializers import (
|
||||||
|
JobDetailSerializer,
|
||||||
JobSerializer,
|
JobSerializer,
|
||||||
JobOptionsSerializer,
|
JobOptionsSerializer,
|
||||||
)
|
)
|
||||||
@@ -14,6 +16,7 @@ from awx.api.serializers import (
|
|||||||
from awx.main.models import (
|
from awx.main.models import (
|
||||||
Label,
|
Label,
|
||||||
Job,
|
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.UnifiedJobTemplateSerializer.get_related', lambda x,y: {})
|
||||||
@mock.patch('awx.api.serializers.JobOptionsSerializer.get_related', lambda x,y: {})
|
@mock.patch('awx.api.serializers.JobOptionsSerializer.get_related', lambda x,y: {})
|
||||||
class TestJobSerializerGetRelated():
|
class TestJobSerializerGetRelated():
|
||||||
|
|
||||||
@pytest.mark.parametrize("related_resource_name", [
|
@pytest.mark.parametrize("related_resource_name", [
|
||||||
'job_events',
|
'job_events',
|
||||||
'relaunch',
|
'relaunch',
|
||||||
@@ -76,6 +80,7 @@ class TestJobSerializerGetRelated():
|
|||||||
@mock.patch('awx.api.serializers.BaseSerializer.to_representation', lambda self,obj: {
|
@mock.patch('awx.api.serializers.BaseSerializer.to_representation', lambda self,obj: {
|
||||||
'extra_vars': obj.extra_vars})
|
'extra_vars': obj.extra_vars})
|
||||||
class TestJobSerializerSubstitution():
|
class TestJobSerializerSubstitution():
|
||||||
|
|
||||||
def test_survey_password_hide(self, mocker):
|
def test_survey_password_hide(self, mocker):
|
||||||
job = mocker.MagicMock(**{
|
job = mocker.MagicMock(**{
|
||||||
'display_extra_vars.return_value': '{\"secret_key\": \"$encrypted$\"}',
|
'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: {})
|
@mock.patch('awx.api.serializers.BaseSerializer.get_summary_fields', lambda x,y: {})
|
||||||
class TestJobOptionsSerializerGetSummaryFields():
|
class TestJobOptionsSerializerGetSummaryFields():
|
||||||
|
|
||||||
def test__summary_field_labels_10_max(self, mocker, job_template, labels):
|
def test__summary_field_labels_10_max(self, mocker, job_template, labels):
|
||||||
job_template.labels.all = mocker.MagicMock(**{'return_value': 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):
|
def test_labels_exists(self, test_get_summary_fields, job_template):
|
||||||
test_get_summary_fields(JobOptionsSerializer, job_template, 'labels')
|
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 == {}
|
||||||
|
|||||||
Reference in New Issue
Block a user