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

Code before:
    Special view to facilitate processing job output in the UI.
    In order to collapse events and their children, the UI needs to know how
    many children exist for a given event.
    This view returns a JSON object where the key is the event counter, and the value
    is the number of children (and grandchildren) events.
    Only events with children are included in the output.

    e.g. Demo Job Template job
    given:
        tuple(event counter, uuid, parent_uuid)
        (1, '4598d19e-93b4-4e33-a0ae-b387a7348964', '')
        (2, 'aae0d189-e3cb-102a-9f00-000000000006', '4598d19e-93b4-4e33-a0ae-b387a7348964')
        (3, 'aae0d189-e3cb-102a-9f00-00000000000c', 'aae0d189-e3cb-102a-9f00-000000000006')
        (4, 'f4194f14-e406-4124-8519-0fdb08b18f4b', 'aae0d189-e3cb-102a-9f00-00000000000c')
        (5, '39f7ad99-dbf3-41e0-93f8-9999db4004f2', 'aae0d189-e3cb-102a-9f00-00000000000c')
        (6, 'aae0d189-e3cb-102a-9f00-000000000008', 'aae0d189-e3cb-102a-9f00-000000000006')
        (7, '39a49992-5ca4-4b6c-b178-e56d0b0333da', 'aae0d189-e3cb-102a-9f00-000000000008')
        (8, '504f3b28-3ea8-4f6f-bd82-60cf8e807cc0', 'aae0d189-e3cb-102a-9f00-000000000008')
        (9, 'a242be54-ebe6-4021-afab-f2878bff2e9f', '4598d19e-93b4-4e33-a0ae-b387a7348964')

    output:
        {
        "1": 8,
        "2": 6,
        "3": 2,
        "6": 2
        }
    event counter 1 has 8 children
    event counter 2 has 6 children
    etc.
    '''

    def get(self, request, **kwargs):
        job = get_object_or_404(models.Job, pk=kwargs['pk'])
        if job.status in ("waiting", "pending", "running"):
            return Response({})
        events = job.get_event_queryset().values_list('counter', 'uuid', 'parent_uuid').order_by('counter')
        # key is uuid, value is number of total children (including children of children, etc.)
        map_uuid_children_tally = dict()
        # key is uuid, value is event counter (the event order)
        map_uuid_counter = dict()
        # key is uuid, value is parent uuid. Used as a quick lookup
        map_uuid_puuid = dict()
        # ultimately we want to return dict(counter: tally), not dict(uuid: tally),
        # so we build up this dict from the map_uuid_children_tally
        map_counter_children_tally = dict()

        def update_grandparents(uuid):
            # recursive function that updates the tally for grandparent events
            parent = map_uuid_puuid.get(uuid, None)
            if parent is None:

Review comment (suggestion):
Even if this method can run in a reasonable amount of time for a high-output job, the _number of keys_ is going to make it difficult to return in the response. If there are a million events, will it even be able to _transmit_ the response before the uWSGI worker dies? As a whole-job endpoint, I find it doubtful that this endpoint will survive the test of real users.

Code after:
class GroupJobEventsList(BaseJobEventsList):

    parent_model = models.Group


class JobJobEventsList(BaseJobEventsList):

    parent_model = models.Job
    pagination_class = UnifiedJobEventPagination

    def get_queryset(self):
        job = self.get_parent_object()
        self.check_parent_access(job)
        return job.get_event_queryset().select_related('host').order_by('start_line')


class JobJobEventsChildrenSummary(APIView):

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

    def get(self, request, **kwargs):
        resp = dict(children_summary={}, meta_event_nested_uuid={}, event_processing_finished=False)
        job = get_object_or_404(models.Job, pk=kwargs['pk'])
        if not job.event_processing_finished:
            return Response(resp)
        else:
            resp["event_processing_finished"] = True

        events = list(job.get_event_queryset().values('counter', 'uuid', 'parent_uuid', 'event').order_by('counter'))
        if len(events) == 0:
            return Response(resp)

        # 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