nodes: 0, module; 1, function_definition; 2, function_name:_serve_runs; 3, parameters; 4, block; 5, identifier:self; 6, identifier:request; 7, expression_statement; 8, if_statement; 9, return_statement; 10, comment:"""Serve a JSON array of run names, ordered by run started time.
  
      Sort order is by started time (aka first event time) with empty times sorted
      last, and then ties are broken by sorting on the run name.
      """; 11, attribute; 12, block; 13, else_clause; 14, call; 15, identifier:self; 16, identifier:_db_connection_provider; 17, expression_statement; 18, expression_statement; 19, expression_statement; 20, comment:# Python's list.sort is stable, so to order by started time and; 21, comment:# then by name, we can just do the sorts in the reverse order.; 22, block; 23, attribute; 24, argument_list; 25, assignment; 26, assignment; 27, assignment; 28, expression_statement; 29, function_definition; 30, expression_statement; 31, identifier:http_util; 32, identifier:Respond; 33, identifier:request; 34, identifier:run_names; 35, string; 36, identifier:db; 37, call; 38, identifier:cursor; 39, call; 40, identifier:run_names; 41, list_comprehension; 42, assignment; 43, function_name:get_first_event_timestamp; 44, parameters; 45, block; 46, call; 47, string_content:application/json; 48, attribute; 49, argument_list; 50, attribute; 51, argument_list; 52, subscript; 53, for_in_clause; 54, identifier:run_names; 55, call; 56, identifier:run_name; 57, try_statement; 58, attribute; 59, argument_list; 60, identifier:self; 61, identifier:_db_connection_provider; 62, identifier:db; 63, identifier:execute; 64, string; 65, identifier:row; 66, integer:0; 67, identifier:row; 68, identifier:cursor; 69, identifier:sorted; 70, argument_list; 71, block; 72, except_clause; 73, identifier:run_names; 74, identifier:sort; 75, keyword_argument; 76, string_content:SELECT
            run_name,
            started_time IS NULL as started_time_nulls_last,
            started_time
          FROM Runs
          ORDER BY started_time_nulls_last, started_time, run_name; 77, call; 78, return_statement; 79, as_pattern; 80, block; 81, identifier:key; 82, identifier:get_first_event_timestamp; 83, attribute; 84, argument_list; 85, call; 86, identifier:ValueError; 87, as_pattern_target; 88, expression_statement; 89, comment:# Put runs without a timestamp at the end.; 90, return_statement; 91, attribute; 92, identifier:Runs; 93, attribute; 94, argument_list; 95, identifier:e; 96, call; 97, call; 98, identifier:self; 99, identifier:_multiplexer; 100, attribute; 101, identifier:FirstEventTimestamp; 102, identifier:run_name; 103, attribute; 104, argument_list; 105, identifier:float; 106, argument_list; 107, identifier:self; 108, identifier:_multiplexer; 109, identifier:logger; 110, identifier:warn; 111, string; 112, identifier:run_name; 113, identifier:e; 114, string; 115, string_content:Unable to get first event timestamp for run %s: %s; 116, string_content:inf
code: def _serve_runs(self, request):
      """Serve a JSON array of run names, ordered by run started time.
  
      Sort order is by started time (aka first event time) with empty times sorted
      last, and then ties are broken by sorting on the run name.
      """
      if self._db_connection_provider:
        db = self._db_connection_provider()
        cursor = db.execute('''
          SELECT
            run_name,
            started_time IS NULL as started_time_nulls_last,
            started_time
          FROM Runs
          ORDER BY started_time_nulls_last, started_time, run_name
        ''')
        run_names = [row[0] for row in cursor]
      else:
        # Python's list.sort is stable, so to order by started time and
        # then by name, we can just do the sorts in the reverse order.
        run_names = sorted(self._multiplexer.Runs())
        def get_first_event_timestamp(run_name):
          try:
            return self._multiplexer.FirstEventTimestamp(run_name)
          except ValueError as e:
            logger.warn(
                'Unable to get first event timestamp for run %s: %s', run_name, e)
            # Put runs without a timestamp at the end.
            return float('inf')
        run_names.sort(key=get_first_event_timestamp)
      return http_util.Respond(request, run_names, 'application/json')