diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 829b0fd2f1..2c400943ee 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -2209,6 +2209,44 @@ class InventoryUpdateSerializer(UnifiedJobSerializer, InventorySourceOptionsSeri return res +class InventoryUpdateDetailSerializer(InventoryUpdateSerializer): + + source_project = serializers.SerializerMethodField( + help_text=_('The project used for this job.'), + method_name='get_source_project_id' + ) + + class Meta: + model = InventoryUpdate + fields = ('*', 'source_project',) + + def get_source_project(self, obj): + return getattrd(obj, 'source_project_update.unified_job_template', None) + + def get_source_project_id(self, obj): + return getattrd(obj, 'source_project_update.unified_job_template.id', None) + + def get_related(self, obj): + res = super(InventoryUpdateDetailSerializer, self).get_related(obj) + source_project_id = self.get_source_project_id(obj) + + if source_project_id: + res['source_project'] = self.reverse('api:project_detail', kwargs={'pk': source_project_id}) + return res + + def get_summary_fields(self, obj): + summary_fields = super(InventoryUpdateDetailSerializer, self).get_summary_fields(obj) + summary_obj = self.get_source_project(obj) + + if summary_obj: + summary_fields['source_project'] = {} + for field in SUMMARIZABLE_FK_FIELDS['project']: + value = getattr(summary_obj, field, None) + if value is not None: + summary_fields['source_project'][field] = value + return summary_fields + + class InventoryUpdateListSerializer(InventoryUpdateSerializer, UnifiedJobListSerializer): class Meta: diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index 2a3e85007a..8b1bd4f97d 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -1972,7 +1972,7 @@ class InventoryInventorySourcesUpdate(RetrieveAPIView): details['status'] = None if inventory_source.can_update: update = inventory_source.update() - details.update(InventoryUpdateSerializer(update, context=self.get_serializer_context()).to_representation(update)) + details.update(InventoryUpdateDetailSerializer(update, context=self.get_serializer_context()).to_representation(update)) details['status'] = 'started' details['inventory_update'] = update.id successes += 1 @@ -2135,7 +2135,7 @@ class InventorySourceUpdateView(RetrieveAPIView): headers = {'Location': update.get_absolute_url(request=request)} data = OrderedDict() data['inventory_update'] = update.id - data.update(InventoryUpdateSerializer(update, context=self.get_serializer_context()).to_representation(update)) + data.update(InventoryUpdateDetailSerializer(update, context=self.get_serializer_context()).to_representation(update)) return Response(data, status=status.HTTP_202_ACCEPTED, headers=headers) else: return self.http_method_not_allowed(request, *args, **kwargs) @@ -2150,7 +2150,7 @@ class InventoryUpdateList(ListAPIView): class InventoryUpdateDetail(UnifiedJobDeletionMixin, RetrieveDestroyAPIView): model = InventoryUpdate - serializer_class = InventoryUpdateSerializer + serializer_class = InventoryUpdateDetailSerializer class InventoryUpdateCredentialsList(SubListAPIView): diff --git a/awx/main/tests/unit/api/test_views.py b/awx/main/tests/unit/api/test_views.py index 369dbc6581..e2f1f5150b 100644 --- a/awx/main/tests/unit/api/test_views.py +++ b/awx/main/tests/unit/api/test_views.py @@ -113,7 +113,7 @@ class TestInventoryInventorySourcesUpdate: with mocker.patch.object(InventoryInventorySourcesUpdate, 'get_object', return_value=obj): with mocker.patch.object(InventoryInventorySourcesUpdate, 'get_serializer_context', return_value=None): - with mocker.patch('awx.api.views.InventoryUpdateSerializer') as serializer_class: + with mocker.patch('awx.api.views.InventoryUpdateDetailSerializer') as serializer_class: serializer = serializer_class.return_value serializer.to_representation.return_value = {} diff --git a/awx/ui/client/features/output/details.component.js b/awx/ui/client/features/output/details.component.js index 60b3130a98..3cece3143a 100644 --- a/awx/ui/client/features/output/details.component.js +++ b/awx/ui/client/features/output/details.component.js @@ -187,6 +187,12 @@ function getInventorySourceDetails () { return null; } + if (resource.model.get('summary_fields.inventory_source.source') === 'scm') { + // we already show a SOURCE PROJECT item for scm inventory updates, so we + // skip the display of the standard source details item. + return null; + } + const { source } = resource.model.get('summary_fields.inventory_source'); const choices = mapChoices(resource.model.options('actions.GET.source.choices')); @@ -324,6 +330,33 @@ function getProjectUpdateDetails (updateId) { return { link, tooltip }; } +function getInventoryScmDetails (updateId, updateStatus) { + const projectId = resource.model.get('summary_fields.source_project.id'); + const projectName = resource.model.get('summary_fields.source_project.name'); + const jobId = updateId || resource.model.get('source_project_update'); + const status = updateStatus || resource.model.get('summary_fields.inventory_source.status'); + + if (!projectId || !projectName) { + return null; + } + + const label = strings.get('labels.INVENTORY_SCM'); + const jobLink = `/#/jobs/project/${jobId}`; + const link = `/#/projects/${projectId}`; + const jobTooltip = strings.get('tooltips.INVENTORY_SCM_JOB'); + const tooltip = strings.get('tooltips.INVENTORY_SCM'); + + let icon; + + if (status === 'unknown') { + icon = 'fa icon-job-pending'; + } else { + icon = `fa icon-job-${status}`; + } + + return { label, link, icon, jobLink, jobTooltip, tooltip, value: projectName }; +} + function getSCMRevisionDetails () { const label = strings.get('labels.SCM_REVISION'); const value = resource.model.get('scm_revision'); @@ -718,6 +751,7 @@ function JobDetailsController ( vm.projectUpdate = getProjectUpdateDetails(); vm.projectStatus = getProjectStatusDetails(); vm.scmRevision = getSCMRevisionDetails(); + vm.inventoryScm = getInventoryScmDetails(); vm.playbook = getPlaybookDetails(); vm.resultTraceback = getResultTracebackDetails(); vm.launchedBy = getLaunchedByDetails(); @@ -748,12 +782,13 @@ function JobDetailsController ( vm.toggleLabels = toggleLabels; vm.showLabels = showLabels; - unsubscribe = subscribe(({ status, started, finished, scm, environment }) => { + unsubscribe = subscribe(({ status, started, finished, scm, inventoryScm, environment }) => { vm.started = getStartDetails(started); vm.finished = getFinishDetails(finished); vm.projectUpdate = getProjectUpdateDetails(scm.id); vm.projectStatus = getProjectStatusDetails(scm.status); vm.environment = getEnvironmentDetails(environment); + vm.inventoryScm = getInventoryScmDetails(inventoryScm.id, inventoryScm.status); vm.status = getStatusDetails(status); vm.job.status = status; }); diff --git a/awx/ui/client/features/output/details.partial.html b/awx/ui/client/features/output/details.partial.html index c56772dbee..dc979923dd 100644 --- a/awx/ui/client/features/output/details.partial.html +++ b/awx/ui/client/features/output/details.partial.html @@ -198,6 +198,27 @@ + +