<commit_before>/**
 * ApiClient class to make calls to different services.
 * This could eventually be a class that other classes more
 * specific to certain endpoints extend and define the url
 * request structure on their own so it can accommodate
 * various endpoint URL structures... a boy can dream.
 * For example: class PhoenixApiClient extends ApiClient { .. }
 *
 * @todo when extending this ApiClient to create more specific
 * service clients, could use Object.assign or Lodash extend()
 * to merge header objects with default headers.
 */
class ApiClient {
  constructor(version, host = window.location.hostname, port = window.location.port) {
    this.headers = {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
    };
    this.host = host;
    this.port = port === '' ? null : port;
    this.version = `/api/${version}/`;
    this.url = `//${this.host}${this.port ? ':' + this.port : ''}${this.version}`;
  }

  /**
   * Check the HTTP status of API response.
   *
   * @param  {Object} response
   * @return {Object}
   * @throws {Object}
   */
  checkStatus(response) {
    if (response.status >= 200 && response.status < 300) {
      return response;
    } else {
      const error = new Error(response.statusText);

      error.response = response;

      throw error;
    }
  }

  /**
   * Send a DELETE request to the given path URI.
   *
   * @param  {String} path
   * @return {Object}
   */
  delete(path) {
    const url = `${this.url}${path}`;

    // @TODO: see todo in post() method!
    console.log('%c ApiClient DELETE Request: \n' + url,
      'background-color: rgba(105,157,215,0.5); color: rgba(33,70,112,1); line-height: 1.5;'
    );

    // @TODO: lots of repeated code with get() and post().
    // Update to use a send() method (created below) to handle
    // all the fetch stuff, and these other methods simple
    // provide options & configurations, etc.
    return window.fetch(url, {
      method: 'DELETE',
      headers: this.headers,
      credentials: 'same-origin'
    })
      .then(this.checkStatus)
      .then(this.parseJson)
      .catch(function(data) {
        console.log(data);
        console.log('um, there was an error! handle that shizzzzz!');
      });
  }

  /**
   * Convert object with query parameters into string.
   *
   * @param  {Object} query
   * @return {String}
   */
  stringifyQuery(query = {}) {
    let urlParams = [];

    Object.keys(query).forEach((key) => {
      urlParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(query[key])}`);
    });

    if (urlParams.length === 0) {
      return '';
    }

    return `?${urlParams.join('&')}`;
  }

  /**
   * Return the JSON data from API response.
   *
   * @param  {Object} response
   * @return {Object}
   */
  parseJson(response) {
    return response.json();
  }

  /**
   * Send a GET request to the given path URI.
   *
   * @param  {String} path
   * @param  {Object} query
   * @return {Object}
   */
  get(path, query = {}) {
    const url = `${this.url}${path}${this.stringifyQuery(query)}`;

    return window.fetch(url, {
      method: 'GET',
      headers: this.headers,
      credentials: 'same-origin'
    })
      .then(this.checkStatus)
      .then(this.parseJson)
      .catch(function(data) {
        console.log(data);
        console.log('um, there was an error! handle that shizzzzz!');
      });
  }

  /**
   * Send a POST request to the given path URI.
   *
   * @param  {String} path
   * @param  {Object} body
   * @param  {Object} headers
   * @return {Object}
   */
  post(path, body = {}) {
    const url = `${this.url}${path}`;

    // @TODO: little experiment with console output for providing more information when
    // API events are happening. console.log() statement are stripped on Prod builds.
    // Might be useful to have a set of defined styles on a JS object that can be passed
    // through: warning, notice, succes, etc...
    console.log('%c ApiClient POST Request Body: \n' + JSON.stringify(body),
      'background-color: rgba(105,157,215,0.5); color: rgba(33,70,112,1); line-height: 1.5;'
    );

    return window.fetch(url, {
      method: 'POST',
      headers: this.headers,
      credentials: 'same-origin',
      body: JSON.stringify(body)
    })
      .then(this.checkStatus)
      .then(this.parseJson)
      .then(function(response) {
        return response;
    });
  }

  send() {
    // @TODO: setup this send method that all other request methods call.
  }
}

export default ApiClient;<commit_msg>Updates to cleanup the ApiClient and DRY-ify it.<commit_after>const isEmpty = require('lodash/isEmpty');

/**
 * ApiClient class to make calls to different services.
 * This could eventually be a class that other classes more
 * specific to certain endpoints extend and define the url
 * request structure on their own so it can accommodate
 * various endpoint URL structures... a boy can dream.
 * For example: class PhoenixApiClient extends ApiClient { .. }
 */
class ApiClient {
  constructor(version, host = window.location.hostname, port = window.location.port) {
    this.headers = {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
    };
    this.host = host;
    this.port = port === '' ? null : port;
    this.version = `/api/${version}/`;
    this.url = `//${this.host}${this.port ? ':' + this.port : ''}${this.version}`;
  }

  /**
   * Check the HTTP status of API response.
   *
   * @param  {Object} response
   * @return {Object}
   * @throws {Object}
   */
  checkStatus(response) {
    if (response.status >= 200 && response.status < 300) {
      return response;
    } else {
      const error = new Error(response.statusText);

      error.response = response;

      throw error;
    }
  }

  /**
   * Send a DELETE request to the given path URI.
   *
   * @param  {String} path
   * @return {Object}
   */
  delete(path) {
    const url = `${this.url}${path}`;

    return this.send('DELETE', url);
  }

  /**
   * Convert object with query parameters into string.
   *
   * @param  {Object} query
   * @return {String}
   */
  stringifyQuery(query = {}) {
    let urlParams = [];

    Object.keys(query).forEach((key) => {
      urlParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(query[key])}`);
    });

    if (urlParams.length === 0) {
      return '';
    }

    return `?${urlParams.join('&')}`;
  }

  /**
   * Return the JSON data from API response.
   *
   * @param  {Object} response
   * @return {Object}
   */
  parseJson(response) {
    return response.json();
  }

  /**
   * Send a GET request to the given path URI.
   *
   * @param  {String} path
   * @param  {Object} query
   * @return {Object}
   */
  get(path, query = {}) {
    const url = `${this.url}${path}${this.stringifyQuery(query)}`;

    return this.send('GET', url);
  }

  /**
   * Send a POST request to the given path URI.
   *
   * @param  {String} path
   * @param  {Object} body
   * @param  {Object} headers
   * @return {Object}
   */
  post(path, body = {}) {
    const url = `${this.url}${path}`;

    return this.send('POST', url, {
      body: JSON.stringify(body)
    });
  }

  /**
   * Send an API request using fetch() and return response.
   *
   * @param  {String} method
   * @param  {String} url
   * @param  {Object} options
   * @return {Promise}
   */
  send(method, url, options = {}) {
    const defaults = {
      method: method,
      headers: this.headers,
      credentials: 'same-origin'
    };

    if (! isEmpty(options)) {
      Object.assign(defaults, options);
    }

    console.log('%c ApiClient Request! \n',
      'background-color: rgba(105,157,215,0.5); color: rgba(33,70,112,1); line-height: 1.5;'
    );

    return window.fetch(url, defaults)
      .then(this.checkStatus)
      .then(this.parseJson)
      .catch((data) => {
        console.log(data);
        console.log('um, there was an error! handle that shizzzzz!');
      });
  }
}

export default ApiClient;
{'fraction_non_alphanumeric': 0.15221909768920405, 'fraction_numerical': 0.009414353832986918, 'mean_word_length': 2.6307146027518864, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 8, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1464438', 'n_tokens_mistral': 2645, 'n_tokens_neox': 2597, 'n_words': 706}