Source: retrieve-and-rank/v1.js

/**
 * Copyright 2015 IBM Corp. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

const fs = require('fs');
const url = require('url');
const requestFactory = require('../lib/requestwrapper');
const solr = require('solr-client');
const helper = require('../lib/helper');
const pick = require('object.pick');
const omit = require('object.omit');
const isStream = require('isstream');
const util = require('util');
const BaseService = require('../lib/base_service');

/**
 *
 * @param options
 * @constructor
 */
function RetrieveAndRankV1(options) {
  BaseService.call(this, options);
}
util.inherits(RetrieveAndRankV1, BaseService);
RetrieveAndRankV1.prototype.name = 'retrieve_and_rank';
RetrieveAndRankV1.prototype.version = 'v1';
RetrieveAndRankV1.URL = 'https://gateway.watsonplatform.net/retrieve-and-rank/api';

/**
 * Creates a ranker
 */
RetrieveAndRankV1.prototype.createRanker = function(params, callback) {
  params = params || {};

  if (!params || !params.training_data) {
    callback(new Error('Missing required parameters: training_data'));
    return;
  }
  if (typeof params.training_data !== 'string' && !isStream(params.training_data)) {
    callback(new Error('training_data needs to be a String or Stream'));
    return;
  }

  const parameters = {
    options: {
      url: '/v1/rankers',
      method: 'POST',
      json: true,
      formData: {
        training_data: params.training_data,
        training_metadata: params.training_metadata || JSON.stringify(omit(params, ['training_data']))
      }
    },
    defaultOptions: this._options
  };
  return requestFactory(parameters, callback);
};

/**
 * Returns the ranked candidates
 *
 * @param {Object} params - everything except answer_data and answers will be passed in as answer_metadata
 * @param {String|ReadableStream} params.answer_data
 * @param {Number} [params.answers] - number of answers to return
 * @param {Function} callback
 */
RetrieveAndRankV1.prototype.rank = function(params, callback) {
  params = params || {};

  if (!params || !params.answer_data) {
    callback(new Error('Missing required parameters: answer_data'));
    return;
  }
  if (typeof params.answer_data !== 'string' && !isStream(params.answer_data)) {
    callback(new Error('answer_data needs to be a String or Stream'));
    return;
  }

  const topLevelParams = ['answer_data', 'answers'];
  const formData = pick(params, topLevelParams);
  formData.answer_metadata = JSON.stringify(omit(params, topLevelParams));

  const parameters = {
    options: {
      url: '/v1/rankers/{ranker_id}/rank',
      method: 'POST',
      json: true,
      formData: formData,
      path: pick(params, ['ranker_id'])
    },
    requiredParams: ['ranker_id'],
    defaultOptions: this._options
  };
  return requestFactory(parameters, callback);
};

/**
 * Returns the training status of the ranker
 */
RetrieveAndRankV1.prototype.rankerStatus = function(params, callback) {
  params = params || {};

  const parameters = {
    options: {
      url: '/v1/rankers/{ranker_id}',
      method: 'GET',
      json: true,
      path: params
    },
    requiredParams: ['ranker_id'],
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

/**
 * Retrieves the list of rankers for the user
 */
RetrieveAndRankV1.prototype.listRankers = function(params, callback) {
  const parameters = {
    options: {
      url: '/v1/rankers',
      method: 'GET',
      json: true
    },
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

/**
 * Deletes a ranker
 */
RetrieveAndRankV1.prototype.deleteRanker = function(params, callback) {
  params = params || {};

  const parameters = {
    options: {
      url: '/v1/rankers/{ranker_id}',
      method: 'DELETE',
      path: params,
      json: true
    },
    requiredParams: ['ranker_id'],
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

// Solr cluster lifecycle operations

/**
 * Lists all Solr clusters associated with the service instance.
 *
 * @param params An Object representing the parameters for this service call.
 *   This request currently does not require any parameters.
 *
 * @param callback The callback.
 */
RetrieveAndRankV1.prototype.listClusters = function(params, callback) {
  params = params || {};

  const parameters = {
    options: {
      url: '/v1/solr_clusters',
      method: 'GET',
      json: true
    },
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

/**
 * Creates a Solr cluster.
 *
 * @param params An Object representing the parameters for this service call.
 *   Optional params
 *     - cluster_name: name to use for identifying the cluster in responses
 *     - cluster_size: size of the cluster to create
 *
 * @param callback The callback.
 */
RetrieveAndRankV1.prototype.createCluster = function(params, callback) {
  params = params || {};

  const parameters = {
    options: {
      url: '/v1/solr_clusters',
      method: 'POST',
      json: true,
      body: params
    },
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

/**
 * Checks whether the specified Solr cluster is ready for use.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to poll
 *
 * @param callback The callback.
 */
RetrieveAndRankV1.prototype.pollCluster = function(params, callback) {
  params = params || {};

  const parameters = {
    options: {
      url: '/v1/solr_clusters/{cluster_id}',
      method: 'GET',
      path: params,
      json: true
    },
    requiredParams: ['cluster_id'],
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

/**
 * Change the size of the Solr cluster.

 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to poll
 *     - cluster_size: the integer size to resize the cluster to
 *
 * @param callback The callback.
 */
RetrieveAndRankV1.prototype.resizeCluster = function(params, callback) {
  params = params || {};

  const parameters = {
    options: {
      url: '/v1/solr_clusters/{cluster_id}/cluster_size',
      method: 'PUT',
      body: { cluster_size: params.cluster_size },
      path: params,
      json: true
    },
    requiredParams: ['cluster_id', 'cluster_size'],
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

/**
 * Get the status of a resize request.

 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to poll
 *
 * @param callback The callback.
 */
RetrieveAndRankV1.prototype.getResizeStatus = function(params, callback) {
  params = params || {};

  const parameters = {
    options: {
      url: '/v1/solr_clusters/{cluster_id}/cluster_size',
      method: 'GET',
      path: params,
      json: true
    },
    requiredParams: ['cluster_id'],
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

/**
 * Deletes a Solr cluster.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to delete
 *
 * @param callback The callback.
 */
RetrieveAndRankV1.prototype.deleteCluster = function(params, callback) {
  params = params || {};

  const parameters = {
    options: {
      url: '/v1/solr_clusters/{cluster_id}',
      method: 'DELETE',
      path: params,
      json: true
    },
    requiredParams: ['cluster_id'],
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

/**
 * Get memory and disk usage stats from a Solr cluster
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to get stats from
 *
 * @param callback The callback.
 */
RetrieveAndRankV1.prototype.getClusterStats = function(params, callback) {
  params = params || {};

  const parameters = {
    options: {
      url: '/v1/solr_clusters/{cluster_id}/stats',
      method: 'GET',
      path: params,
      json: true
    },
    requiredParams: ['cluster_id'],
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

// Solr config operations

/**
 * Lists the configuration sets in ZooKeeper.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to get configuration from
 *
 * @param callback The callback.
 */
RetrieveAndRankV1.prototype.listConfigs = function(params, callback) {
  params = params || {};

  const parameters = {
    options: {
      url: '/v1/solr_clusters/{cluster_id}/config',
      method: 'GET',
      path: params,
      json: true
    },
    requiredParams: ['cluster_id'],
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

/**
 * Upload a configuration set to ZooKeeper.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to upload configuration to
 *     - config_name: the name of the config in ZooKeeper referenced when creating a collection
 *     - config_zip_path: the ZIP file to upload
 *
 * @param callback The callback.
 */
RetrieveAndRankV1.prototype.uploadConfig = function(params, callback) {
  params = params || {};

  if (!params || !params.config_zip_path) {
    callback(new Error('Missing required parameters: config_zip_path'));
    return;
  }
  let configFile = null;
  if (typeof params.config_zip_path === 'string') {
    configFile = fs.createReadStream(params.config_zip_path);
  } else if (isStream(params.config_zip_path)) {
    configFile = params.config_zip_path;
  } else {
    callback(new Error('config_zip_path needs to be a String or Stream'));
    return;
  }

  const parameters = {
    options: {
      url: '/v1/solr_clusters/{cluster_id}/config/{config_name}',
      method: 'POST',
      path: params
    },
    requiredParams: ['cluster_id', 'config_name'],
    defaultOptions: this._options
  };

  return configFile
    .on('response', function(response) {
      // Replace content-type
      response.headers['content-type'] = 'application/zip';
    })
    .pipe(requestFactory(parameters, callback));
};

/**
 * Get a configuration set from ZooKeeper as a .zip file.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to get configuration from
 *     - config_name: the name of the config in ZooKeeper
 *
 * @param callback The callback.
 */
RetrieveAndRankV1.prototype.getConfig = function(params, callback) {
  params = params || {};

  const parameters = {
    options: {
      url: '/v1/solr_clusters/{cluster_id}/config/{config_name}',
      method: 'GET',
      path: params,
      json: true,
      headers: {
        accept: 'application/zip'
      }
    },
    requiredParams: ['cluster_id', 'config_name'],
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

/**
 * Delete a configuration set in ZooKeeper.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to delete configuration from
 *     - config_name: the name of the config in ZooKeeper
 *
 * @param callback The callback.
 */
RetrieveAndRankV1.prototype.deleteConfig = function(params, callback) {
  params = params || {};

  const parameters = {
    options: {
      url: '/v1/solr_clusters/{cluster_id}/config/{config_name}',
      method: 'DELETE',
      path: params,
      json: true
    },
    requiredParams: ['cluster_id', 'config_name'],
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

// Solr collection operations

/**
 * List all collections for a Solr cluster.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to list collections from
 *   Optional params:
 *     - wt: the writer type for the response, defaults to 'json'
 *
 * @param callback The callback.
 */
RetrieveAndRankV1.prototype.listCollections = function(params, callback) {
  params = params || {};
  const parameters = {
    options: {
      url: '/v1/solr_clusters/{cluster_id}/solr/admin/collections',
      method: 'GET',
      qs: {
        action: 'LIST',
        wt: params.wt || 'json'
      },
      path: params,
      json: true
    },
    requiredParams: ['cluster_id'],
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

/**
 * Create a Solr collection.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to create the collection on
 *     - collection_name: the name of the collection to create
 *     - config_name: the name of the config in ZooKeeper
 *   Optional params:
 *     - wt: the writer type for the response, defaults to 'json'
 *
 * @param callback The callback.
 */
RetrieveAndRankV1.prototype.createCollection = function(params, callback) {
  params = params || {};

  const missingParams = helper.getMissingParams(params, ['cluster_id', 'collection_name', 'config_name']);
  if (missingParams) {
    callback(missingParams);
    return;
  }

  const queryParams = {
    'collection.configName': params.config_name,
    name: params.collection_name,
    wt: params.wt || 'json',
    action: 'CREATE'
  };

  const parameters = {
    options: {
      url: '/v1/solr_clusters/{cluster_id}/solr/admin/collections',
      method: 'POST',
      qs: queryParams,
      path: pick(params, ['cluster_id']),
      json: true
    },
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

/**
 * Delete a Solr collection.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to delete the collection on
 *     - collection_name: the name of the collection to delete
 *   Optional params:
 *     - wt: the writer type for the response, defaults to 'json'
 *
 * @param callback The callback.
 */
RetrieveAndRankV1.prototype.deleteCollection = function(params, callback) {
  params = params || {};

  const missingParams = helper.getMissingParams(params, ['cluster_id', 'collection_name']);
  if (missingParams) {
    callback(missingParams);
    return;
  }

  const queryParams = {
    name: params.collection_name,
    wt: params.wt || 'json',
    action: 'DELETE'
  };

  const parameters = {
    options: {
      url: '/v1/solr_clusters/{cluster_id}/solr/admin/collections',
      method: 'POST',
      qs: queryParams,
      path: pick(params, ['cluster_id']),
      json: true
    },
    defaultOptions: this._options
  };

  return requestFactory(parameters, callback);
};

/**
 * Get a Solr client for indexing and searching documents.
 * See https://github.com/lbdremy/solr-node-client for documentation and examples.
 *
 * @param params An Object representing the parameters for this service call.
 *   Required params:
 *     - cluster_id: the ID of the Solr cluster to delete the collection on
 *     - collection_name: the name of the collection for indexing/searching
 */
RetrieveAndRankV1.prototype.createSolrClient = function(params) {
  params = params || {};

  const missingParams = helper.getMissingParams(params, ['cluster_id', 'collection_name']);
  if (missingParams) {
    throw missingParams;
  }

  const serviceUrl = url.parse(this._options.url);
  const apiPath = serviceUrl.path === '/' ? '' : serviceUrl.path || '';

  const solrClient = solr.createClient({
    host: serviceUrl.hostname,
    path: apiPath + '/v1/solr_clusters/' + params.cluster_id + '/solr',
    port: serviceUrl.port || '443',
    secure: true,
    core: params.collection_name
  });

  if (this._options.username && this._options.password) {
    solrClient.basicAuth(this._options.username, this._options.password);
  }
  return solrClient;
};

module.exports = RetrieveAndRankV1;