code: function points_from_grouped_response(response, info) {
      var points = [];
      var is_date_histogram = _is_date_histogram(response.aggregations);
      function points_from_group(aggregation, fields, point_base) {
          if (!aggregation) { return; }
          var field = fields.shift();
          var bucket = aggregation[field];
          _assert_nonempty_group(bucket, field);
          if (fields.length === 0) {
              var new_points = bucket.buckets.map(function base_case(b) {
                  var pt = _aggregation_values(b, b.doc_count, info);
                  pt[field] = b.key_as_string || b.key;
  
                  // Following Juttle 'reduce' behavior, we don't output points
                  // for an empty batch if the reducer has 'groupby'
                  var should_return_point = !(is_date_histogram && info.grouping.length && !b.doc_count);
  
                  return should_return_point && _.extend(pt, point_base);
              });
  
              points = points.concat(_.compact(new_points));
          } else {
              bucket.buckets.forEach(function extend_base_and_recurse(b) {
                  var base = _.clone(point_base);
                  base[field] = b.key;
                  points_from_group(b, _.clone(fields), base);
              });
          }
      }
  
      var grouping = _.clone(info.grouping);
      if (is_date_histogram) {
          grouping.push('time');
      }
  
      points_from_group(response.aggregations, grouping, {});
  
      return _time_sort(points);
  }
docs: see samples/es-grouped-aggregation-response for a response from read | reduce -every :3s: avg(bytes) by clientip, httpversion basically it's a tree where each node specifies the value of a grouped-by key and the leaves have the results of any reducers this function traverses the tree and yields its Juttle points (date histograms are basically "group by time")