diff --git a/awx/api/serializers.py b/awx/api/serializers.py index e79f2e3ad0..c621481712 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -1419,6 +1419,48 @@ class ProjectUpdateSerializer(UnifiedJobSerializer, ProjectOptionsSerializer): return res +class ProjectUpdateDetailSerializer(ProjectUpdateSerializer): + + 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 = ProjectUpdate + fields = ('*', 'host_status_counts', 'playbook_counts',) + + def get_playbook_counts(self, obj): + task_count = obj.project_update_events.filter(event='playbook_on_task_start').count() + play_count = obj.project_update_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.project_update_events.only('event_data').get(event='playbook_on_stats').event_data + except ProjectUpdateEvent.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 ProjectUpdateListSerializer(ProjectUpdateSerializer, UnifiedJobListSerializer): pass diff --git a/awx/api/views.py b/awx/api/views.py index f7a939e839..e2f913bd14 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -1416,7 +1416,7 @@ class ProjectUpdateList(ListAPIView): class ProjectUpdateDetail(UnifiedJobDeletionMixin, RetrieveDestroyAPIView): model = ProjectUpdate - serializer_class = ProjectUpdateSerializer + serializer_class = ProjectUpdateDetailSerializer class ProjectUpdateEventsList(SubListAPIView): diff --git a/awx/main/tests/unit/api/serializers/test_job_serializers.py b/awx/main/tests/unit/api/serializers/test_job_serializers.py index d3fd514ecc..5688a845ec 100644 --- a/awx/main/tests/unit/api/serializers/test_job_serializers.py +++ b/awx/main/tests/unit/api/serializers/test_job_serializers.py @@ -11,12 +11,14 @@ from awx.api.serializers import ( JobDetailSerializer, JobSerializer, JobOptionsSerializer, + ProjectUpdateDetailSerializer, ) from awx.main.models import ( Label, Job, JobEvent, + ProjectUpdateEvent, ) @@ -142,10 +144,52 @@ class TestJobDetailSerializerGetHostStatusCountFields(object): assert host_status_counts == {'ok': 1, 'changed': 1, 'dark': 2} - def test_host_status_counts_is_empty_dict_without_stats_event(self, job, mocker): + def test_host_status_counts_is_empty_dict_without_stats_event(self, job): job.job_events = JobEvent.objects.none() serializer = JobDetailSerializer() host_status_counts = serializer.get_host_status_counts(job) assert host_status_counts == {} + + +class TestProjectUpdateDetailSerializerGetHostStatusCountFields(object): + + def test_hosts_are_counted_once(self, project_update, mocker): + mock_event = ProjectUpdateEvent(**{ + '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)) + project_update.project_update_events.only = mocker.MagicMock(return_value=mock_qs) + + serializer = ProjectUpdateDetailSerializer() + host_status_counts = serializer.get_host_status_counts(project_update) + + assert host_status_counts == {'ok': 1, 'changed': 1, 'dark': 2} + + def test_host_status_counts_is_empty_dict_without_stats_event(self, project_update): + project_update.project_update_events = ProjectUpdateEvent.objects.none() + + serializer = ProjectUpdateDetailSerializer() + host_status_counts = serializer.get_host_status_counts(project_update) + + assert host_status_counts == {}