Repository: ansible/awx
File: awx/api/views/__init__.py (Python)

Code before:
        playbook_on_start
          verbose
          playbook_on_play_start
            playbook_on_task_start
              runner_on_start        <- A
                verbose              <- this maps to the uuid of A
                verbose
                runner_on_ok
              playbook_on_task_start <- B
                runner_on_start
                runner_on_ok
                verbose              <- this maps to the uuid of B
                verbose
              playbook_on_stats

    The output will include a JSON object where the key is the event counter,
    and the value is the assigned nested uuid.
    '''

    renderer_classes = [JSONRenderer]
    meta_events = ('debug', 'verbose', 'warning', 'error', 'system_warning', 'deprecated')

    def get(self, request, **kwargs):
        job = get_object_or_404(models.Job, pk=kwargs['pk'])
        if not job.event_processing_finished:
            return Response({})
        events = list(job.get_event_queryset().values('counter', 'uuid', 'parent_uuid', 'event').order_by('counter'))
        if len(events) == 0:
            return Response({})
        # key is counter, value is number of total children (including children of children, etc.)
        map_counter_children_tally = {i['counter']: {"rowNumber": 0, "numChildren": 0} for i in events}
        # key is uuid, value is counter
        map_uuid_counter = {i['uuid']: i['counter'] for i in events}
        # key is uuid, value is parent uuid. Used as a quick lookup
        map_uuid_puuid = {i['uuid']: i['parent_uuid'] for i in events}
        # key is counter of meta events (i.e. verbose), value is uuid of the assigned parent
        map_meta_counter_nested_uuid = {}

        prev_non_meta_event = events[0]
        for i, e in enumerate(events):
            if not e['event'] in JobJobEventsChildrenSummary.meta_events:
                prev_non_meta_event = e
            if not e['uuid']:
                continue
            puuid = e['parent_uuid']

            # if event is verbose (or debug, etc), we need to "assign" it a
            # parent. This code looks at the event level of the previous
            # non-verbose event, and level of the next (by looking ahead)
            # non-verbose event. The verbose event is assigned the same parent
            # uuid of the higher level event.

Review comment (suggestion):
Right after this line, I think you should go ahead and write a conditional to give a 400 with too many events. You don't need to load anything, use `job.emitted_events`, already in memory. If that number is greater than your limit, then bail.

How do you know what the limit is? Use the firehose script or whatever you want to get the time taken to load some events. Divide to get seconds per event, and then use that to find out how many events will take 10 seconds to load and make that the limit. This is simple enough that it would be best to do that now and avoid more costly iteration in the future.

Code after:
                    z += 1
                event_level_after = models.JobEvent.LEVEL_FOR_EVENT[next_non_meta_event['event']]
                if event_level_after and event_level_after > event_level_before:
                    puuid = next_non_meta_event['parent_uuid']
                else:
                    puuid = prev_non_meta_event['parent_uuid']
                if puuid:
                    map_meta_counter_nested_uuid[e['counter']] = puuid
            map_counter_children_tally[e['counter']]['rowNumber'] = i
            if not puuid:
                continue
            # now traverse up the parent, grandparent, etc. events and tally those
            while puuid:
                map_counter_children_tally[map_uuid_counter[puuid]]['numChildren'] += 1
                puuid = map_uuid_puuid.get(puuid, None)

        # create new dictionary, dropping events with 0 children
        resp["children_summary"] = {k: v for k, v in map_counter_children_tally.items() if v['numChildren'] != 0}
        resp["meta_event_nested_uuid"] = map_meta_counter_nested_uuid
        return Response(resp)


class AdHocCommandList(ListCreateAPIView):

    model = models.AdHocCommand
    serializer_class = serializers.AdHocCommandListSerializer
    always_allow_superuser = False

    @transaction.non_atomic_requests
    def dispatch(self, *args, **kwargs):
        return super(AdHocCommandList, self).dispatch(*args, **kwargs)

    def update_raw_data(self, data):
        # Hide inventory and limit fields from raw data, since they will be set
        # automatically by sub list create view.
        parent_model = getattr(self, 'parent_model', None)
        if parent_model in (models.Host, models.Group):
            data.pop('inventory', None)
            data.pop('limit', None)
        return super(AdHocCommandList, self).update_raw_data(data)

    def create(self, request, *args, **kwargs):
        # Inject inventory ID and limit if parent objects is a host/group.
        if hasattr(self, 'get_parent_object') and not getattr(self, 'parent_key', None):
            data = request.data
            # HACK: Make request data mutable.
            if getattr(data, '_mutable', None) is False:
                data._mutable = True
            parent_obj = self.get_parent_object()
            if isinstance(parent_obj, (models.Host, models.Group)):
                data['inventory'] = parent_obj.inventory_id