Manual Reference Source Test

src/services/FlowService.js

import { deprecate } from 'core-decorators';
import Client from '../Client';
import Flow from '../helpers/flow/Flow';
import FlowKeyValue from '../helpers/flow/FlowKeyValue';
import JsonApiService from './JsonApiService';
import {
  hasAttribute,
  hasToken,
  isA,
  isRequired,
  isValid,
  validate,
} from '../utilities/ValidationUtility';
// import FlowKeyValue from '../helpers/flow/FlowKeyValue';

/**
 * Service handling Flow process calls with specific endpoints.
 *
 * The Clinical6 server can create flows or multi page forms that enable the colleciton of data.
 * The SDK allows you to retrieve the information stored about each flow process so that it can be
 * integrated into the app.
 */
class FlowService extends JsonApiService {
  /**
   * Update type to be data_collection__captured_value_groups
   */
  constructor() {
    super();

    /** @type {String} - The type of the default data */
    // this.type = 'data_collection__captured_value_groups';
    this.type = 'data_collection__flow_processes';
  }

  /**
   * Collects data from the flow process via a id.
   *
   * Should really use the {@link Flow} methods instead that call this method.
   *
   * @throws {Clinical6Error}        - If missing token or missing required parameters
   * @param  {!Flow}     flow        - Flow process to which collect from
   * @param  {!String}   flow.id     - The permanent link for the requested flow process.
   * @param  {?Object[]} flow.fields - Collected data from users
   * @param  {?Object}   [options]   - Various options to change the way data is saved.
   * @return {Promise}               - Promise object that returns success or failure
   *
   * @example
   * import { flowService } from 'clinical6';
   * flowService.getFlow('favorite_flow').then(flow => {
   *  flowService.collect(flow);  // not the best way to do this.
   *  flow.collect();  // this is the way to do this
   * });
   */
  collect(flow, options = {}) {
    validate('FlowService.collect',
      hasToken(),
      isRequired({ flow }),
      hasAttribute({ flow }, 'id'),
      hasAttribute({ flow }, '_fields'));

    return this.collectFields(flow, flow.fields, options);
  }


  /**
   * Collects data outside the flow process.  This is used for per step saves.
   *
   * Should really use the {@link Flow} methods instead that call this method.
   *
   * @throws {Clinical6Error}      - If missing token or missing required parameters
   * @param  {!Flow}     flow      - Flow process to which collect from
   * @param  {!String}   flow.id   - The permanent link for the requested flow process.
   * @param  {!Object[]} fields    - Collected data from users
   * @return {Promise}             - Promise object that returns success or failure
   */
  collectFields(flow, fields) {
    validate('FlowService.collectFields',
      hasToken(),
      isRequired({ flow }),
      hasAttribute({ flow }, 'id'));

    const attributes = {};
    fields.forEach(field => (attributes[field.input_id] = field.value));
    const params = {
      data: {
        type: 'data_collection__flow_process_values',
        attributes,
        relationships: {
          flow_process: {
            data: {
              id: flow.id, // number
              type: 'data_collection__flow_processes'
            }
          },
          owner: {
            data: {
              id: flow.owner || Client.instance.user.id,
              type: flow.ownerType
            }
          }
        }
      }
    };
    if (flow.flowDataGroupId) {
      params.data.relationships.captured_value_group = {
        data: {
          id: flow.flowDataGroupId,
          type: 'data_collection__captured_value_groups'
        }
      };
    }

    // const fkv = new FlowKeyValue(params);
    // fields.forEach(field => (fkv[field.input_id] = field.value));
    // fkv.flow = flow;
    // fkv.owner = flow.owner;
    // if (flow.flowDataGroup) {
    //   fkv.flowDataGroup = flow.flowDataGroup;
    // }

    let promise;
    if (flow.entry) {
      promise = Client.instance.fetch(`/v3/ediary/entries/${flow.entry.id}/data_collection/flow_process_values`, 'post', params);
    } else {
      params.data.relationships.owner.data.type = (flow.ownerType === 'mobile_user') ? 'mobile_users' : flow.ownerType;
      const fkv = new FlowKeyValue(params);
      promise = fkv.save().then((result) => {
        if (result.flowDataGroup && result.flowDataGroup.id) {
          flow.flowDataGroupId = result.flowDataGroup.id;
        }
        return result;
      });
    }
    return promise;
  }


  /**
   * Delete (soft delete / hide) a flow process value resource given an id
   *
   * http://clinical6-docs.s3-website-us-east-1.amazonaws.com/apidoc/v3data_collectionflow_process_values/destroy.html
   *
   * @throws {Clinical6Error}       - If missing token or missing required parameters
   * @param  {!Object}  keyValue    - The process value object used to save information
   * @param  {!Number}  keyValue.id - The flow id, must be a number
   * @param  {?String}  [cacheMode] - A string to indicate how this is cached.
   * @return {Promise<any>}         - Promise object that returns success or failure
   *
   * @example
   * import { FlowKeyValue, flowService } from 'clinical6';
   *
   * const v = new FlowKeyValue({ id: 5 });
   * const response = await flowService.deleteKeyValue(v);
   *
   * @example
   * import { flowService } from 'clinical6';
   *
   * const response = await flowService.deleteKeyValue({ id: 5 });
   */
  async deleteKeyValue(keyValue, cacheMode = undefined) {
    const errorTitle = 'FlowService.deleteKeyValue';
    validate(errorTitle,
      hasToken(),
      isRequired({ keyValue }),
      hasAttribute({ keyValue }, 'id'));
    if (!keyValue.type) {
      keyValue.type = 'data_collection__flow_process_values';
    }
    return super.delete(keyValue, { cacheMode });
  }

  /**
   * @override
   * Call a GET request expecting JSON API information.
   *
   * @throws {Clinical6Error}                 - If missing token or missing required parameters
   * @param  {Object} [params]                - Parameters used to get information from server (such as id)
   * @param  {Number} [params.id]             - Id of the flow
   * @param  {Number} [params.permanentLink]  - permanentLink of the flow
   * @param  {String} [cacheMode]             - Override the caching method for this call
   * @return {Promise<Flow[] | Flow>}         - Promise object that returns one flow or an array
   *
   * @example
   * import { flowService } from 'clinical6';
   *
   * // You will be able to access these flows using the `get` method.
   * flowService.get().then(flows => console.log(flows));
   *
   * // Additionally, you can retrieve a specific flow with the `get` method, using the permanent link of the desired flow
   * // as a parameter.
   * flowService.get({ permanentLink: 'favorite_flow' }).then(flow => console.log(flow));
   */
  get(params = {}, cacheMode = undefined) {
    validate('FlowService.get',
      hasToken());
    return (params.permanentLink) ? this.get(params.permanentLink) : super.get(params, { cacheMode });
  }

  /**
   * Gets the flow process from a id.
   *
   * The best way to pull back a flow process is by using the id or resource provided by the server.
   * To get a collection of Flows or Sections which may contain Flows is using the
   * {@link StatusService#getSections} method.  This will automatically call the
   * {@link Section#connectChildren} method if it has children which calls the
   * {@link Section#getData} method calling this method ({@link FlowService#getFlow}).
   *
   * @throws {Clinical6Error}         - If missing token or missing required parameters
   * @param  {!String} permanentLink  - The permanent link for the requested flow process.
   * @param  {?Number} [page=1]       - The page number of the results given.
   * @param  {?Number} [perPage=0]    - The number of content to return per page.
   * @return {Promise<Flow>}          - Promise object that returns success or failure
   *
   * @example
   * import { flowService } from 'clinical6';
   * flowService.getFlow('favorite_flow');
   */
  getFlow(permanentLink, page = 1, perPage = 0) {
    validate('FlowService.getFlow',
      hasToken(),
      isRequired({ permanentLink }),
      isA({ permanentLink }, 'string'));
    return new Promise((resolve, reject) => {
      let httpQuery = `?page=${page}&per_page=${perPage}`;
      httpQuery = ``;
      Client.instance.fetch(`/api/v2/data_collection/flow_processes/${permanentLink}${httpQuery}`).then(
        response => resolve(new Flow(Object.assign({}, { id: permanentLink }, response))),
        err => reject(err)
      )
        .catch(err => reject(err));
    });
  }

  /**
   * Get Flow Data Group based on an id.
   *
   * @throws {Clinical6Error}       - If missing token or missing required parameters
   * @param  {!Object}  params      - Parameters used to get information from server (such as id)
   * @param  {String}   [cacheMode] - Override the caching method for this call
   * @return {Promise<FlowDataGroup>} - Promise object that returns one object
   *
   * @example
   * import { flowService, FlowDataGroup } from 'clinical6';
   * flowService.getDataGroup({ id: 24 });
   * const capturedValueGroup = new FlowDataGroup( {id: 24 });
   * flowService.getDataGroup(capturedValueGroup);
   */
  @deprecate
  getDataGroup(params, cacheMode = undefined) {
    validate('FlowService.getDataGroup',
      hasToken(),
      isRequired({ params }),
      hasAttribute({ params }, 'id'));
    validate('FlowService.getDataGroup',
      hasToken(),
      isRequired({ id: params.id }),
      isA({ id: params.id }, 'number'));
    const url = `/v3/data_collection/captured_value_groups/${params.id}`;
    return super.get({ id: params.id, type: 'data_collection__captured_value_groups' }, { url, cacheMode });
  }

  /**
   * Gets input data for a flow.
   *
   * Should really use the {@link Flow} methods instead that call this method.
   *
   * @throws {Clinical6Error} - If missing token or missing required parameters
   * @param  {!Flow} flow     - Flow process from which data comes
   * @return {Promise}        - Promise object that returns success or failure
   *
   * @example
   * import { flowService } from 'clinical6';
   * flowService.getFlow('favorite_flow').then(flow => {
   *  flowService.getInputDataByFlow(flow);
   * });
   */
  getInputDataByFlow(flow) {
    validate('FlowService.getInputData',
      hasToken(),
      isRequired({ flow }),
      hasAttribute({ flow }, 'id'));

    const id = flow.permanentLink;
    const params = { };
    if (flow.options.owner) {
      params.owner = flow.options.owner;
    }
    if (flow.options.owner_type) {
      params.owner_type = flow.options.owner_type;
    }
    if (flow.options.existing_id) {
      params.existing_id = flow.options.existing_id;
    }
    if (flow.entry && flow.entry.captured_value_group && flow.entry.captured_value_group.id) {
      params.captured_value_group_id = flow.entry.captured_value_group.id;
    }

    return new Promise((resolve, reject) => {
      Client.instance.fetch(
        `/api/v2/data_collection/flow_processes/${id}/captured_values/retrieve_all`,
        'POST', params
      ).then(
        (response) => {
          response.captured_values.forEach(c => flow.set(c.input_id, c.value));
          resolve(response.captured_values);
        },
        err => reject(err)
      );
    });
  }

  /**
   * Gets the input data for a specific input id.
   *
   * Should really use the {@link Flow} methods instead that call this method.
   *
   * @throws {Clinical6Error}  - If missing token or missing required parameters
   * @param  {!String} inputId - Flow Input ID of the input from which data comes
   * @return {Promise}         - Promise object that returns success or failure
   */
  getInputDataById(inputId) {
    validate('FlowService.getInputData',
      hasToken(),
      isRequired({ inputId }),
      isA({ inputId }, 'string'));

    Client.instance.fetch(`/api/collected_values/${inputId}`);
  }

  /**
   * Call a GET request expecting JSON API information.
   *
   * @throws {Clinical6Error}     - If missing token or missing required parameters
   * @param  {Object} [params]    - Parameters used to get information from server (such as id)
   * @param  {String} [cacheMode] - Override the caching method for this call
   * @return {Promise<FlowKeyValue[] | FlowKeyValue>}
   *                              - Promise object that returns one keyValue or a keyValue array
   *
   * @example
   * import { flowService } from 'clinical6';
   *
   * // You will be able to access these keyValues using the `getKeyValue` method.
   * flowService.getKeyValue().then(keyValues => console.log(keyValues));
   *
   * // Additionally, you can retrieve a specific keyValue with the `getKeyValue`
   * // method, using the ID of the desired keyValue as a parameter.
   * flowService.getKeyValue({id: 23}).then(keyValue => console.log(keyValue));
   */
  getKeyValue(params = {}, cacheMode = undefined) {
    validate('FlowService.getKeyValue',
      hasToken());
    if (!params.type) {
      params.type = 'data_collection__flow_process_values';
    }
    return super.get(params, { cacheMode });
  }

  /**
   * @override
   * Call a POST request on the main obj.type expecting JSON API information.
   *
   * @param  {Object} [flow]     - Parameters used to get information from server (such as id)
   * @param  {String} [cacheMode] - Override the caching method for this call
   * @return {Promise<Flow>}     - Inserted flow
   *
   * @example
   * import { Flow, flowService } from 'clinical6';
   * const flow = new Flow({...});
   *
   * // you can insert a flow using the `insert` method.
   * flowService.insert(flow).then(flow => console.log(flow));
   *
   * // you could also just call `save` on the flow if it doesn't have an id, which will also
   * // invoke the `insert` method
   * flow.save();
   */
  insert(flow, cacheMode = undefined) {
    validate('FlowService.insert',
      hasToken(),
      isRequired({ flow }));

    return super.insert(flow, { cacheMode });
  }

  /**
   * Inserts the process values to start a flow data group / captured value group.  This is how a flow starts and generates
   * a captured value group id.  If the group id already exists, this should also save it (though this is going to change)
   *
   * http://clinical6-docs.s3-website-us-east-1.amazonaws.com/apidoc/v3data_collectionflow_process_values/create.html
   *
   * @throws {Clinical6Error}                             - If missing token or missing required parameters
   * @param  {!FlowKeyValue}  keyValue                    - The process value object used to save information
   * @param  {!Flow}          keyValue.flow               - The flow being started
   * @param  {!Number}        keyValue.flow.id            - The flow id, must be a number
   * @param  {?Entry}         [keyValue.flow.entry]       - The flow entry
   * @param  {!Number}        keyValue.flow.entry.id      - The flow entry id, must be a number
   * @param  {!Object}        keyValue.owner              - The owner of the data being collected
   * @param  {!String}        keyValue.owner.type         - The owner type, defaults to current logged in user type
   *                                                        ('mobile_users' or 'users')
   * @param  {!String}        keyValue.owner.id           - The owner id, defaults to current logged in user's id
   * @param  {!Object}        [keyValue.flowDataGroup]    - The flow data group (captured value group)
   * @param  {!Number}        keyValue.flowDataGroup.id   - The flow data group id (captured value group id)
   * @param  {?String}        [cacheMode]                 - A string to indicate how this is cached.
   * @return {Promise<FlowKeyValue}                       - Promise object that returns the resulting flow process value
   *                                                        (with flow data group id / captured value group id)
   *
   * @example
   * import { Flow, FlowDataGroup, FlowKeyValue, flowService } from 'clinical6';
   *
   * const v = new FlowKeyValue(requestJsonApi);
   * v.flow = new Flow({ id: 94 });
   * v.flow.store();
   * v.owner = new User({ id: 59 });
   * v.owner.store();
   * v.flowDataGroup = new FlowDataGroup({ id: 23 });
   * v.flowDataGroup.store();
   * v['56'] = 'male';
   * v['57'] = 15;
   * const response = await flowService.insertKeyValue(v);
   */
  insertKeyValue(keyValue, cacheMode = undefined) {
    const errorTitle = 'FlowService.insertKeyValue';
    validate(errorTitle,
      hasToken(),
      isRequired({ keyValue }),
      hasAttribute({ keyValue }, 'flow'),
      isValid((keyValue instanceof FlowKeyValue), 'keyValue is not a type of FlowKeyValue'));
    const { flow } = keyValue;
    const owner = keyValue.owner || Client.instance.user;
    validate(errorTitle,
      isRequired({ flow, owner }),
      hasAttribute({ flow, owner }, 'id'),
      hasAttribute({ owner }, 'type'),
      isA({ flowId: flow.id, ownerId: owner.id }, 'number'));

    // If there is an entry, use the ediary flow process
    const { entry } = flow;
    let url = `/v3/data_collection/flow_process_values`;
    if (entry) {
      validate(errorTitle,
        hasAttribute({ entry }, 'id'),
        isA({ entryId: entry.id }, 'number'));
      url = `/v3/ediary/entries/${entry.id}/data_collection/flow_process_values`;
    }
    return super.insert(keyValue, { url, cacheMode });
  }

  /**
   * Transitions flow from one status to another.
   *
   * Should really use the {@link Flow} methods instead that call this method, such as
   * {@link Flow#transition}, {@link Flow#start} or {@link Flow#complete}.  Additionally, some of
   * the {@link FlowStep} transition methods will trigger this as well.
   *
   * @throws {Clinical6Error}     - If missing token or missing required parameters
   * @param  {!Flow}   flow       - Flow process to which collect from
   * @param  {!String} flow.id    - The permanent link for the requested flow process.
   * @param  {!String} transition - the action to be applied to transition the flow
   * @param  {?Object} [options]  - Various options to change the way the transition occurs
   * @return {Promise}            - Promise object that returns success or failure
   *
   * @example
   * import { flowService } from 'clinical6';
   * flowService.getFlow('favorite_flow').then(flow => {
   *  flowService.transition(flow, 'accept');
   * });
   */
  transition(flow, transition, options = {}) {
    validate('FlowService.transition',
      hasToken(),
      isRequired({ flow, transition }),
      hasAttribute({ flow }, 'id'),
      isA({ transition }, 'string'));

    const status = Object.assign({}, options, {
      object: flow.permanentLink,
      object_type: 'data_collection/flow_process',
      transition,
    });

    return Client.instance.fetch(`/api/v2/data_collection/flow_processes/${flow.permanentLink}/collect`,
      'post', { status });
  }

  /**
   * @override
   * Call a POST request on the main obj.type expecting JSON API information.
   *
   * @param  {Object} [flow]     - Parameters used to get information from server (such as id)
   * @param  {String} [cacheMode] - Override the caching method for this call
   * @return {Promise<Flow>}     - Inserted flow
   *
   * @example
   * import { Flow, flowService } from 'clinical6';
   * const flow = new Flow({...});
   *
   * // you can insert a flow using the `insert` method.
   * flowService.insert(flow).then(flow => console.log(flow));
   *
   * // you could also just call `save` on the flow if it doesn't have an id, which will also
   * // invoke the `insert` method
   * flow.save();
   */
  update(flow, cacheMode = undefined) {
    validate('FlowService.update',
      hasToken(),
      isRequired({ flow }),
      hasAttribute({ flow }, 'id'));

    return super.update(flow, { cacheMode });
  }
}

export default FlowService;