Source: components/report.js

/**
 *  @overview provides methods for fetching reports via CloudHealth's API
 *  @see {@link https://github.com/CloudHealth/cht_api_guide#reporting-api}
 *  @author Ben Watson <ben.watson@coxautoinc.com>
 */

var utils = require('../utils/chapi');
var async = require('async');

/**
 *  @class Report
 *  @memberof module:cox-chapi
 *  @param {string} [api_key]
 */
function Report(api_key) {
  this.set_api_key(api_key);
}

/**
 *  sets the api key to use when making calls to CloudHealth's API
 *  @function module:cox-chapi.Report#set_api_key
 *  @param {string} api_key
 */
Report.prototype.set_api_key = function set_api_key(api_key) {
  this._api_key = api_key;
}

/**
 *  creates and returns an options object that can be given to utils.send_request
 *  @private
 *  @param {string} method - the method to use (ie. GET, POST, etc.)
 *  @param {string} [path] - a string to append to the path field of options
 *  @param {string[]} [params] - an array of parameters to add to the url in the
 *    form "key=value"
 *  @return {object} an options object
 */
Report.prototype._options = function _options(method, path, params) {
  return utils._options('/olap_reports', method, path, params, this._api_key);
};

/**
 *  gets a list of topics (or reports for a topic if a topic id is specified)
 *  @function module:cox-chapi.Report#list
 *  @param {object} [flags] - an optional object specifying the following options
 *  @param {boolean} [flags.nest] - if true, each topic will have a "reports"
 *    field containing the result of calling this function for that topic
 *  @param {boolean} [flags.all] - same as nest
 *  @param {string} [topic] - the topic to list subtopics for
 *  @param {arrayCallback} cb - called with an array of objects, each containing
 *    a name and id field for a topic
 */
Report.prototype.list = function list(flags, topic, cb) {
  if (typeof flags === 'function') {
    cb = flags;
    topic = '';
    flags = undefined;
  }
  else if (typeof topic === 'function') {
    cb = topic;
    topic = '';
    if (typeof flags === 'string') {
      topic = '/' + flags;
      flags = undefined;
    }
  }
  else {
    topic = '/' + topic;
  }

  this._list(flags, topic, cb);
};

/**
 *  Helper for #list
 *  @private
 */
Report.prototype._list = function _list(flags, topic, cb) {
  if (flags && (flags.nest || flags.all)) {
    this._nest(cb);
  }
  else {
    var options = this._options('GET', topic);

    utils.send_request(options, null, this._list_cb.bind(this, flags, topic, cb));
  }
};

/**
 *  Helper callback for #list
 *  @private
 */
Report.prototype._list_cb = function _list_cb(flags, topic, cb, err, result) {
  if (err) return cb(err, result);

  var topics = this._format_list(result);
  cb(null, topics);
};

/**
 *  gets a list of topics, each having an array of reports named "reports"
 *  @private
 *  @function module:cox-chapi.Report#_nest
 *  @param {arrayCallback} cb - called with the list of topics
 */
Report.prototype._nest = function _nest(cb) {
  this.list((err, topics) => {
    if (err) return cb(err);

    var list_calls = [];
    for (var i=0; i<topics.length; i++) {
      var topic = topics[i];
      list_calls.push(((index, topic, list_cb) => {
        this.list(topic.id, (err, reports) => {
          if (err) return list_cb(err);
          list_cb(null, {index: index, reports: reports});
        });
      }).bind(this, i, topic));
    }
    async.parallel(list_calls, (err, reports) => {
      if (err) return cb(err);

      for (var report of reports) {
        topics[report.index].reports = report.reports;
      }
      cb(null, topics);
    });
  });
};

/**
 *  Formats the result of calling "list"
 *  @private
 *  @function module:cox-chapi.Report#_format_list
 *  @param {object} result - the result of calling "list"
 *  @return {array} an array of topics extracted from the result
 */
Report.prototype._format_list = function _format_list(result) {
  var links = result.links;
  var keys = Object.keys(links);
  var topics = [];
  for (var i=0; i<keys.length; ++i) {
    var key = keys[i];
    var topic = {
      name: key,
    }
    var match = links[key].href.match(/report_id=([0-9]+)/);
    if (match) {
      topic.id = match[1];
    }
    else {
      topic.id = links[key].href.match(/olap_reports\/(.+)$/)[1];
    }
    topics.push(topic);
  }
  return topics;
};

/**
 *  gets data for the report with the given id
 *  @function module:cox-chapi.Report#get
 *  @param {string} id - the id of the report
 *  @param {objectCallback} cb - yields the report data
 */
Report.prototype.get = function get(id, cb) {
  if ((''+id).match(/^[0-9]+$/)) {
    id = `custom/${id}`;
  }

  var options = this._options('GET', '/' + id);

  utils.send_request(options, null, cb);
};

/**
 *  lists the possible dimensions for generating a report under a base
 *  @function module:cox-chapi.Report#dimensions
 *  @param {object} [flags] - an object containing flags
 *  @param {boolean} [flags.short] - specifies to yield only the name and label
 *    of dimensions and measures
 *  @param {string} base - the category/sub-category for the report to show in
 *    the form "category/sub-category" (ie. "usage/instance" or "cost/history")
 *  @param {objectCallback} cb - yields an object containing dimensions (used
 *    for x-axis or category) and measures (used for y-axis)
 */
Report.prototype.dimensions = function dimensions(flags, base, cb) {
  if (typeof flags !== 'object') {
    cb = base;
    base = flags;
    flags = undefined;
  }

  this._dimensions(flags, base, cb);
};

/**
 *  Helper for #dimensions
 *  @private
 */
Report.prototype._dimensions = function _dimensions(flags, base, cb) {
  var options = this._options('GET', '/' + base + '/new');

  utils.send_request(options, null, this._dimensions_cb.bind(this, flags, base, cb));
};

/**
 *  Helper callback for #dimensions
 *  @private
 */
Report.prototype._dimensions_cb = function _dimensions_cb(flags, base, cb, err, result) {
  if (err) return cb(err, result);

  if (flags && flags.short) {
    result.dimensions = result.dimensions.map((dimension) =>
      ({label: dimension.label, name: dimension.name})
    );
    result.measures = result.measures.map((measure) =>
      ({label: measure.label, name: measure.name})
    );
  }

  cb(null, result);
};

/**
 *  returns data for a custom report built from the parameters you specify
 *  @function module:cox-chapi.Report#generate
 *  @param {string} base - the category/sub-category for the report to show in
 *    the form "category/sub-category" (ie. "usage/instance" or "cost/history")
 *  @param {string} x - the id of the dimension to use for the x-axis
 *  @param {string} y - the id of the measure to use for the y-axis
 *  @param {string} category - the id of the dimension to use for categorizing
 *    the report's data
 *  @param {stirng} [interval] - the time interval by which to break up the data
 *    (only use if either the x or category dimensions are "time")
 *  @param {objectCallback} cb - yields an object containing data for the report
 *    as well as data about the report (including dimensions, filters, interval,
 *    measures, report name, status, and the last time the report was updated)
 */
Report.prototype.generate = function generate(base, x, y, category, interval, cb) {
  if (typeof interval === 'function') {
    cb = interval;
    interval = undefined;
  }

  this._generate(base, x, y, category, interval, cb);
};

/**
 *  Helper for #generate
 *  @private
 */
Report.prototype._generate = function _generate(base, x, y, category, interval, cb) {
  if (interval && interval !== '') {
    interval = 'interval=' + interval;
  }
  else {
    interval = '';
  }

  var options = this._options('GET', '/' + base, ['dimensions[]=' + x, 'dimensions[]' + category, 'measures[]=' + y, interval]);

  utils.send_request(options, null, cb);
};

module.exports = Report;