diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 1a54312004..8b38895414 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -4955,7 +4955,10 @@ class InstanceGroupSerializer(BaseSerializer): class ActivityStreamSerializer(BaseSerializer): changes = serializers.SerializerMethodField() - object_association = serializers.SerializerMethodField() + object_association = serializers.SerializerMethodField( + help_text=_("When present, shows the field name of the role or relationship that changed.")) + object_type = serializers.SerializerMethodField( + help_text=_("When present, shows the model on which the role or relationship was defined.")) @cached_property def _local_summarizable_fk_fields(self): @@ -4980,8 +4983,8 @@ class ActivityStreamSerializer(BaseSerializer): class Meta: model = ActivityStream - fields = ('*', '-name', '-description', '-created', '-modified', - 'timestamp', 'operation', 'changes', 'object1', 'object2', 'object_association') + fields = ('*', '-name', '-description', '-created', '-modified', 'timestamp', 'operation', + 'changes', 'object1', 'object2', 'object_association', 'object_type') def get_fields(self): ret = super(ActivityStreamSerializer, self).get_fields() @@ -5024,6 +5027,21 @@ class ActivityStreamSerializer(BaseSerializer): logger.debug('Failed to parse activity stream relationship type {}'.format(obj.object_relationship_type)) return "" + def get_object_type(self, obj): + if not obj.object_relationship_type: + return "" + elif obj.object_relationship_type.endswith('_role'): + return camelcase_to_underscore(obj.object_relationship_type.rsplit('.', 2)[-2]) + # default case: these values look like + # "awx.main.models.organization.Organization_notification_templates_success" + # so we have to take after the last period but before the first underscore. + try: + cls = obj.object_relationship_type.rsplit('.', 1)[0] + return camelcase_to_underscore(cls.split('_', 1)) + except Exception: + logger.debug('Failed to parse activity stream relationship type {}'.format(obj.object_relationship_type)) + return "" + def get_related(self, obj): rel = {} if obj.actor is not None: diff --git a/awx/ui/client/src/activity-stream/factories/build-description.factory.js b/awx/ui/client/src/activity-stream/factories/build-description.factory.js index 20fcb4c7b3..ddb05b0662 100644 --- a/awx/ui/client/src/activity-stream/factories/build-description.factory.js +++ b/awx/ui/client/src/activity-stream/factories/build-description.factory.js @@ -17,6 +17,16 @@ export default function BuildDescription(BuildAnchor, $log, i18n) { switch(activity.object_association){ // explicit role dis+associations case 'role': + var object1 = activity.object1; + var object2 = activity.object2; + + // if object1 winds up being the role's resource, we need to swap the objects + // in order to make the sentence make sense. + if (activity.object_type === object1) { + object1 = activity.object2; + object2 = activity.object1; + } + // object1 field is resource targeted by the dis+association // object2 field is the resource the role is inherited from // summary_field.role[0] contains ref info about the role @@ -24,23 +34,23 @@ export default function BuildDescription(BuildAnchor, $log, i18n) { // expected outcome: "disassociated role_name from " case 'disassociate': if (isGroupRelationship(activity)){ - activity.description += BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) + activity.summary_fields.role[0].role_field + - ' from ' + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity); + activity.description += BuildAnchor(activity.summary_fields.group[1], object2, activity) + activity.summary_fields.role[0].role_field + + ' from ' + BuildAnchor(activity.summary_fields.group[0], object1, activity); } else{ - activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field + - ' from ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity); + activity.description += BuildAnchor(activity.summary_fields[object2][0], object2, activity) + activity.summary_fields.role[0].role_field + + ' from ' + BuildAnchor(activity.summary_fields[object1][0], object1, activity); } break; // expected outcome: "associated role_name to " case 'associate': if (isGroupRelationship(activity)){ - activity.description += BuildAnchor(activity.summary_fields.group[1], activity.object2, activity) + activity.summary_fields.role[0].role_field + - ' to ' + BuildAnchor(activity.summary_fields.group[0], activity.object1, activity); + activity.description += BuildAnchor(activity.summary_fields.group[1], object2, activity) + activity.summary_fields.role[0].role_field + + ' to ' + BuildAnchor(activity.summary_fields.group[0], object1, activity); } else{ - activity.description += BuildAnchor(activity.summary_fields[activity.object2][0], activity.object2, activity) + activity.summary_fields.role[0].role_field + - ' to ' + BuildAnchor(activity.summary_fields[activity.object1][0], activity.object1, activity); + activity.description += BuildAnchor(activity.summary_fields[object2][0], object2, activity) + activity.summary_fields.role[0].role_field + + ' to ' + BuildAnchor(activity.summary_fields[object1][0], object1, activity); } break; }