static getSPIGraphHierarchy (req, res) {
    if (req.query.exp === undefined) {
      return res.serverError(403, 'Missing exp parameter');
    }

    const fields = [];
    const parts = req.query.exp.split(',');
    for (let i = 0; i < parts.length; i++) {
      if (internals.scriptAggs[parts[i]] !== undefined) {
        fields.push(internals.scriptAggs[parts[i]]);
        continue;
      }
      const field = Config.getFieldsMap()[parts[i]];
      if (!field) {
        return res.serverError(403, `Unknown expression ${parts[i]}\n`);
      }
      fields.push(field);
    }

    SessionAPIs.buildSessionQuery(req, (err, query, indices) => {
      if (err) {
        console.log(`ERROR - ${req.method} /api/spigraphhierarchy`, util.inspect(err, false, 50));
        return res.serverError(403, err);
      }

      query.size = 0; // Don't need any real results, just aggregations
      delete query.sort;
      delete query.aggregations;
      const size = +req.query.size || 20;

      if (!query.query.bool.filter) {
        query.query.bool.filter = [];
      }

      let lastQ = query;
      for (let i = 0; i < fields.length; i++) {
        // Require that each field exists
        query.query.bool.filter.push({ exists: { field: fields[i].dbField } });

        lastQ.aggregations ??= { };
        if (fields[i].script) {
          lastQ.aggregations.field = { terms: { script: { lang: 'painless', source: fields[i].script }, size } };
        } else {
          lastQ.aggregations.field = { terms: { field: fields[i].dbField, size } };
        }

        lastQ = lastQ.aggregations.field;

        lastQ.aggregations ??= { };
        lastQ.aggregations.srcip = { cardinality: { field: 'source.ip' } };
        lastQ.aggregations.dstip = { cardinality: { field: 'destination.ip' } };
      }

      if (Config.debug > 2) {
        console.log('/api/spigraphhierarchy aggregations', indices, JSON.stringify(query, false, 2));
      }

      const options = ViewerUtils.addCluster(req.query.cluster);

      Db.searchSessions(indices, query, options, (err, result) => {
        if (err) {
          console.log(`ERROR - ${req.method} /api/spigraphhierarchy`, util.inspect(err, false, 50));
          res.status(400);
          return res.end(err);
        }

        if (Config.debug > 2) {
          console.log('/api/spigraphhierarchy result', JSON.stringify(result, false, 2));
        }

        // format the data for the pie graph
        const hierarchicalResults = { name: 'Top Talkers', children: [] };
        function addDataToPie (buckets, addTo) {
          for (let i = 0; i < buckets.length; i++) {
            const bucket = buckets[i];
            const obj = {
              name: bucket.key,
              size: bucket.doc_count
            };
            if (bucket.srcip !== undefined) {
              obj.srcips = bucket.srcip.value;
              obj.dstips = bucket.dstip.value;
            }
            addTo.push(obj);
            if (bucket.field) {
              addTo[i].children = [];
              addTo[i].size = undefined; // size is interpreted from children
              addTo[i].sizeValue = bucket.doc_count; // keep sizeValue for display
              if (bucket.srcip !== undefined) {
                obj.srcips = bucket.srcip.value;
                obj.dstips = bucket.dstip.value;
              }
              addDataToPie(bucket.field.buckets, addTo[i].children);
            }
          }
        }

        // There is 1 entry per row, the entry is determine by the leafs, with an array of parents.
        // This uses a depth first search.
        const tableResults = [];
        function addDataToTable (parents, buckets) {
          for (const bucket of buckets) {
            if (bucket.field) {
              // Not leaf - add this one to parents list, recurse, and remove from parents list
              parents.push({
                name: bucket.key,
                size: bucket.doc_count
              });
              // Make a copy of parents since the callee saves and we modified after calling
              addDataToTable(JSON.parse(JSON.stringify(parents)), bucket.field.buckets);
              parents.pop();
            } else {
              // Leaf - add an entry to tableResults
              tableResults.push({
                name: bucket.key,
                size: bucket.doc_count,
                parents
              });
            }
          }
        }

        addDataToPie(result.aggregations.field.buckets, hierarchicalResults.children);
        addDataToTable([], result.aggregations.field.buckets);

        return res.send({
          success: true,
          tableResults,
          hierarchicalResults
        });
      });
    });
  }
POST/GET - /api/spigraphhierarchy

Builds an elasticsearch session query. Gets a list of values for each field with counts and returns them to the client.
@name /spigraphhierarchy
@param {SessionsQuery} See_List - This API supports a common set of parameters documented in the SessionsQuery section
@param {string} exp - Comma separated list of db fields to populate the graph/table.
@param {boolean} strictly=false - When set the entire session must be inside the date range to be observed, otherwise if it overlaps it is displayed. Overwrites the bounding parameter, sets bonding to 'both'
@returns {object} hierarchicalResults - The nested data to populate the treemap or pie
@returns {array} tableResults - The list data to populate the table