<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import { NavigationActions, StackActions } from 'react-navigation';
import { UIDatabase } from '../database';
import Settings from '../settings/MobileAppSettings';
import { createRecord } from '../database/utilities/index';
import { navStrings } from '../localization/index';
import { SETTINGS_KEYS } from '../settings/index';
import { getCurrentRouteName } from './selectors';
import { ROUTES } from './constants';

/**
 * Navigation Action Creators.
 *
 * This file contains actions related to navigation which can be dispatched
 * to the redux store.
 *
 * Actions should return either a plain object or a thunk for side effects.
 *
 * NavigationActions are consists of action creators supplied by react-navigation.
 *
 * NavigationActions.navigate() - accepts an object as the payload, which should
 * have the fields:
 *
 * - `routeName` (See: pages/index - PAGES - keys are routeNames)
 * - `params` (See: Pages/pageContainer and pages/index FINALISABLE_PAGES for requirements)
 *
 */

export const gotoPrescriptions = () =>
  NavigationActions.navigate({
    routeName: ROUTES.PRESCRIPTIONS,
    params: { title: 'Prescriptions' },
  });

export const gotoDispensingPage = () =>
  NavigationActions.navigate({
    routeName: 'dispensing',
    params: { title: 'Dispensing' },
  });

/**
 * Pushes the Realm explorer route onto the main navigation stack.
 */
export const gotoRealmExplorer = () =>
  NavigationActions.navigate({
    routeName: ROUTES.REALM_EXPLORER,
    params: {
      title: 'Database Contents',
    },
  });

/**
 * Pushes the Customer Invoices route onto the main navigation stack.
 */
export const gotoCustomerInvoices = () =>
  NavigationActions.navigate({
    routeName: ROUTES.CUSTOMER_INVOICES,
    params: {
      title: navStrings.customer_invoices,
    },
  });

/**
 * Pushes the Customer Requisitions route onto the main navigation stack.
 */
export const gotoCustomerRequisitions = () =>
  NavigationActions.navigate({
    routeName: ROUTES.CUSTOMER_REQUISITIONS,
    params: {
      title: navStrings.customer_requisitions,
    },
  });

/**
 * Pushes the Supplier Invoices route onto the main navigation stack.
 */
export const gotoSupplierInvoices = () =>
  NavigationActions.navigate({
    routeName: ROUTES.SUPPLIER_INVOICES,
    params: {
      title: navStrings.supplier_invoices,
    },
  });

/**
 * Pushes the Supplier Requisitions route onto the main navigation stack.
 */
export const gotoSupplierRequisitions = () =>
  NavigationActions.navigate({
    routeName: ROUTES.SUPPLIER_REQUISITIONS,
    params: {
      title: navStrings.supplier_requisitions,
    },
  });

/**
 * Pushes the Stocktakes route onto the main navigation stack.
 */
export const gotoStocktakes = () =>
  NavigationActions.navigate({
    routeName: ROUTES.STOCKTAKES,
    params: {
      title: navStrings.stocktakes,
    },
  });

/**
 * Pushes the Stock route onto the main navigation stack.
 */
export const gotoStock = () =>
  NavigationActions.navigate({
    routeName: ROUTES.STOCK,
    params: {
      title: navStrings.current_stock,
    },
  });

/**
 * Action creator for navigating to the SupplierRequisition screen.
 *
 * @param {Object} requisition The requisition to pass to the next screen.
 */
export const gotoStocktakeManagePage = (stocktakeName, stocktake) => (dispatch, getState) => {
  const { nav } = getState();

  const currentRouteName = getCurrentRouteName(nav);

  const navigationActionCreator =
    currentRouteName === ROUTES.STOCKTAKES ? NavigationActions.navigate : StackActions.replace;

  const navigationParameters = {
    routeName: ROUTES.STOCKTAKE_MANAGER,
    params: {
      title: stocktake ? navStrings.manage_stocktake : navStrings.new_stocktake,
      stocktakeName,
      stocktake,
      pageObject: stocktake,
    },
  };

  dispatch(navigationActionCreator(navigationParameters));
};

/**
 * Navigate to the StocktakeEditPage.
 *
 * @param {Object} stocktake  The requisition to navigate to.
 */
export const gotoStocktakeEditPage = stocktake => (dispatch, getState) => {
  const { nav } = getState();
  const currentRouteName = getCurrentRouteName(nav);

  // If navigating from the stocktakesPage, go straight to the StocktakeEditPage. Otherwise,
  // replace the current page as the user is coming from StocktakeManagePage.
  const navigationActionCreator =
    currentRouteName === ROUTES.STOCKTAKES ? NavigationActions.navigate : StackActions.replace;

  const navigationParameters = {
    routeName: ROUTES.STOCKTAKE_EDITOR,
    params: { title: navStrings.stocktake, stocktake, pageObject: stocktake },
  };

  dispatch(navigationActionCreator(navigationParameters));
};

/**
 * Action creator for navigating to a customer invoice. Ensures the CI is at least
 * confirmed before navigating as if this is not enforced, it is possible for
 * a particular item being issued across multiple invoices in larger quantities
 * than are available.
 *
 * @param {Object} transaction The CI to navigate to.
 * @param {Object} dispatch    Redux dispatch method.
 */
export const gotoCustomerInvoice = transaction => dispatch => {
  const { isConfirmed, isFinalised } = transaction;

  // Customer invoices are generally created with the status confirmed. This handles unexpected
  // cases of an incoming sycned invoice with status 'nw' or 'sg'.
  if (!isConfirmed && !isFinalised) {
    UIDatabase.write(() => {
      transaction.confirm(UIDatabase);
      UIDatabase.save('Transaction', transaction);
    });
  }

  const navigationAction = NavigationActions.navigate({
    routeName: ROUTES.CUSTOMER_INVOICE,
    params: {
      title: `${navStrings.invoice} ${transaction.serialNumber}`,
      transaction,
      pageObject: transaction,
    },
  });

  dispatch(navigationAction);
};

/**
 * Action creator for navigating to a SupplierInvoice. Ensures the SI is finalised, if
 * confirmed before navigating. This should not occur, but if this is not enforced, a
 * user can reduce the amount of stock in a SI which has been used in a CI, which causes
 * inventory adjustments instantly.
 *
 * @param {Object} transaction The SI to navigate to.
 * @param {Object} dispatch    Redux dispatch method.
 */
export const gotoSupplierInvoice = transaction => dispatch => {
  const { isConfirmed } = transaction;

  // Supplier invoices are `new` or `finalised`. Ensure any `confirmed` invoices are
  // `finalised` before navigating.
  if (isConfirmed) {
    UIDatabase.write(() => {
      transaction.finalise(UIDatabase);
      UIDatabase.save('Transaction', transaction);
    });
  }

  const navigationAction = NavigationActions.navigate({
    routeName: ROUTES.SUPPLIER_INVOICE,
    params: {
      title: `${navStrings.invoice} ${transaction.serialNumber}`,
      transaction,
      pageObject: transaction,
    },
  });

  dispatch(navigationAction);
};

/**
 * Navigate to the SupplierRequisitionPage.
 *
 * @param {Object} requisition  SupplierRequisition to navigate to.
 */
export const gotoSupplierRequisition = requisition =>
  NavigationActions.navigate({
    routeName: ROUTES.SUPPLIER_REQUISITION,
    params: {
      title: `${navStrings.requisition} ${requisition.serialNumber}`,
      requisition,
      pageObject: requisition,
    },
  });

/**
 * Navigate to the CustomerRequisitionPage.
 *
 * @param {Object} requisition  Customer requisition to navigate to.
 */
export const gotoCustomerRequisition = requisition =>
  NavigationActions.navigate({
    routeName: ROUTES.CUSTOMER_REQUISITION,
    params: {
      title: `${navStrings.requisition} ${requisition.serialNumber}`,
      requisition,
      pageObject: requisition,
    },
  });

/**
 * Action creator for creating, and navigating to a Supplier Requsition.
 * Requisition is created by a thunk initially.
 *
 * @param {String} CurrentUser The currently logged in user.
 * @param {Object} requisitionParameters Parameters for the to-be-created object.
 * RequisitionParameters can be any fields in Requisition.js to pass to createRecord.
 */
export const createSupplierRequisition = ({
  currentUser,
  ...requisitionParameters
}) => dispatch => {
  // Fetch this stores custom data to find if this store has customized
  // monthsLeadTime.
  const customData = Settings.get(SETTINGS_KEYS.THIS_STORE_CUSTOM_DATA);

  // CustomData is a stringified JSON object.
  const parsedCustomData = customData ? JSON.parse(customData) : '';

  // Months lead time has an effect on daysToSupply for a requisition.
  const monthsLeadTime = parsedCustomData.monthsLeadTime
    ? Number(customData.monthsLeadTime.data)
    : 0;

  // Create the requisition. If a program was supplied, add items from that
  // program, otherwise just navigate to it.
  let requisition;
  UIDatabase.write(() => {
    requisition = createRecord(UIDatabase, 'Requisition', currentUser, {
      ...requisitionParameters,
      monthsLeadTime,
    });

    if (requisition.program) requisition.addItemsFromProgram(UIDatabase);
  });

  dispatch(gotoSupplierRequisition(requisition));
};

/**
 * Creates a stocktake and replaces the current route in the StackNavigator which
 * would be a StocktakeManagePage, with a StocktakeEditPage, with the newly
 * created Stocktake.
 *
 * @param {Object} StocktakeParameters  Parameters for the stocktake to create.
 */
export const createStocktake = ({ currentUser, stocktakeName, program, itemIds }) => dispatch => {
  let stocktake;

  UIDatabase.write(() => {
    stocktake = createRecord(UIDatabase, 'Stocktake', currentUser, stocktakeName, program);
    if (program) stocktake.addItemsFromProgram(UIDatabase);
    else if (itemIds) stocktake.setItemsByID(UIDatabase, itemIds);
  });

  // If creating a Stocktake with a program or with a list of itemIds, navigate to
  // the StocktakeEditPage. Otherwise, navigate to the StocktakeManagePage.
  return program || itemIds
    ? dispatch(gotoStocktakeEditPage(stocktake))
    : dispatch(gotoStocktakeManagePage(stocktakeName, stocktake));
};

/**
 * Action creator which first creates a customer invoice, and then navigates to it
 * for editing.
 *
 * @param {Object} otherParty     The other party of the invoice (Customer)
 * @param {Object} currentUser    The currently logged in user.
 */
export const createCustomerInvoice = (otherParty, currentUser) => dispatch => {
  let newCustomerInvoice;
  UIDatabase.write(() => {
    newCustomerInvoice = createRecord(UIDatabase, 'CustomerInvoice', otherParty, currentUser);
  });

  dispatch(gotoCustomerInvoice(newCustomerInvoice));
};

/**
 * Action creator which first creates a supplier invoice, and then navigates to it
 * for editing.
 *
 * @param {Object} otherParty     The other party of the invoice (Supplier)
 * @param {Object} currentUser    The currently logged in user.
 */
export const createSupplierInvoice = (otherParty, currentUser) => dispatch => {
  let newCustomerInvoice;
  UIDatabase.write(() => {
    newCustomerInvoice = createRecord(UIDatabase, 'SupplierInvoice', otherParty, currentUser);
  });

  dispatch(gotoSupplierInvoice(newCustomerInvoice));
};

/**
 * Updates a Stocktake with the passed array of itemIDs and navigates to a
 * StocktakeEditPage.
 *
 * @param {Object} stocktake realm Stocktake object
 * @param {Array}  itemIds   Array of item id strings that should be the new
 *                           Items in the stocktake.
 * @param {String} name   Name of the stocktake for updating. Cannot update to remove completely.
 */
export const updateStocktake = (stocktake, itemIds, name = '') => dispatch => {
  UIDatabase.write(() => {
    if (name) stocktake.name = name;

    stocktake.setItemsByID(UIDatabase, itemIds);
    UIDatabase.save('Stocktake', stocktake);
  });

  dispatch(gotoStocktakeEditPage(stocktake));
};
<commit_msg>Add create and goto Prescription actions<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import { NavigationActions, StackActions } from 'react-navigation';
import { UIDatabase } from '../database';
import Settings from '../settings/MobileAppSettings';
import { createRecord } from '../database/utilities/index';
import { navStrings } from '../localization/index';
import { SETTINGS_KEYS } from '../settings/index';
import { getCurrentRouteName } from './selectors';
import { ROUTES } from './constants';

/**
 * Navigation Action Creators.
 *
 * This file contains actions related to navigation which can be dispatched
 * to the redux store.
 *
 * Actions should return either a plain object or a thunk for side effects.
 *
 * NavigationActions are consists of action creators supplied by react-navigation.
 *
 * NavigationActions.navigate() - accepts an object as the payload, which should
 * have the fields:
 *
 * - `routeName` (See: pages/index - PAGES - keys are routeNames)
 * - `params` (See: Pages/pageContainer and pages/index FINALISABLE_PAGES for requirements)
 *
 */

/**
 * Action creator which first creates a customer invoice, and then navigates to it
 * for editing.
 *
 * @param {Object} patient     The other party of the invoice (Customer)
 * @param {Object} currentUser    The currently logged in user.
 */
export const createPrescription = (patient, currentUser) => dispatch => {
  let newPrescription;
  UIDatabase.write(() => {
    newPrescription = createRecord(
      UIDatabase,
      'CustomerInvoice',
      patient,
      currentUser,
      'dispensary'
    );
  });

  dispatch(gotoPrescription(newPrescription));
};

export const gotoPrescription = prescription =>
  NavigationActions.navigate({
    routeName: ROUTES.PRESCRIPTION,
    params: {
      title: `Prescription ${prescription.serialNumber}`,
      transaction: prescription,
      pageObject: prescription,
    },
  });

export const gotoPrescriptions = () =>
  NavigationActions.navigate({
    routeName: ROUTES.PRESCRIPTIONS,
    params: { title: 'Prescriptions' },
  });

export const gotoDispensingPage = () =>
  NavigationActions.navigate({
    routeName: 'dispensing',
    params: { title: 'Dispensing' },
  });

/**
 * Pushes the Realm explorer route onto the main navigation stack.
 */
export const gotoRealmExplorer = () =>
  NavigationActions.navigate({
    routeName: ROUTES.REALM_EXPLORER,
    params: {
      title: 'Database Contents',
    },
  });

/**
 * Pushes the Customer Invoices route onto the main navigation stack.
 */
export const gotoCustomerInvoices = () =>
  NavigationActions.navigate({
    routeName: ROUTES.CUSTOMER_INVOICES,
    params: {
      title: navStrings.customer_invoices,
    },
  });

/**
 * Pushes the Customer Requisitions route onto the main navigation stack.
 */
export const gotoCustomerRequisitions = () =>
  NavigationActions.navigate({
    routeName: ROUTES.CUSTOMER_REQUISITIONS,
    params: {
      title: navStrings.customer_requisitions,
    },
  });

/**
 * Pushes the Supplier Invoices route onto the main navigation stack.
 */
export const gotoSupplierInvoices = () =>
  NavigationActions.navigate({
    routeName: ROUTES.SUPPLIER_INVOICES,
    params: {
      title: navStrings.supplier_invoices,
    },
  });

/**
 * Pushes the Supplier Requisitions route onto the main navigation stack.
 */
export const gotoSupplierRequisitions = () =>
  NavigationActions.navigate({
    routeName: ROUTES.SUPPLIER_REQUISITIONS,
    params: {
      title: navStrings.supplier_requisitions,
    },
  });

/**
 * Pushes the Stocktakes route onto the main navigation stack.
 */
export const gotoStocktakes = () =>
  NavigationActions.navigate({
    routeName: ROUTES.STOCKTAKES,
    params: {
      title: navStrings.stocktakes,
    },
  });

/**
 * Pushes the Stock route onto the main navigation stack.
 */
export const gotoStock = () =>
  NavigationActions.navigate({
    routeName: ROUTES.STOCK,
    params: {
      title: navStrings.current_stock,
    },
  });

/**
 * Action creator for navigating to the SupplierRequisition screen.
 *
 * @param {Object} requisition The requisition to pass to the next screen.
 */
export const gotoStocktakeManagePage = (stocktakeName, stocktake) => (dispatch, getState) => {
  const { nav } = getState();

  const currentRouteName = getCurrentRouteName(nav);

  const navigationActionCreator =
    currentRouteName === ROUTES.STOCKTAKES ? NavigationActions.navigate : StackActions.replace;

  const navigationParameters = {
    routeName: ROUTES.STOCKTAKE_MANAGER,
    params: {
      title: stocktake ? navStrings.manage_stocktake : navStrings.new_stocktake,
      stocktakeName,
      stocktake,
      pageObject: stocktake,
    },
  };

  dispatch(navigationActionCreator(navigationParameters));
};

/**
 * Navigate to the StocktakeEditPage.
 *
 * @param {Object} stocktake  The requisition to navigate to.
 */
export const gotoStocktakeEditPage = stocktake => (dispatch, getState) => {
  const { nav } = getState();
  const currentRouteName = getCurrentRouteName(nav);

  // If navigating from the stocktakesPage, go straight to the StocktakeEditPage. Otherwise,
  // replace the current page as the user is coming from StocktakeManagePage.
  const navigationActionCreator =
    currentRouteName === ROUTES.STOCKTAKES ? NavigationActions.navigate : StackActions.replace;

  const navigationParameters = {
    routeName: ROUTES.STOCKTAKE_EDITOR,
    params: { title: navStrings.stocktake, stocktake, pageObject: stocktake },
  };

  dispatch(navigationActionCreator(navigationParameters));
};

/**
 * Action creator for navigating to a customer invoice. Ensures the CI is at least
 * confirmed before navigating as if this is not enforced, it is possible for
 * a particular item being issued across multiple invoices in larger quantities
 * than are available.
 *
 * @param {Object} transaction The CI to navigate to.
 * @param {Object} dispatch    Redux dispatch method.
 */
export const gotoCustomerInvoice = transaction => dispatch => {
  const { isConfirmed, isFinalised } = transaction;

  // Customer invoices are generally created with the status confirmed. This handles unexpected
  // cases of an incoming sycned invoice with status 'nw' or 'sg'.
  if (!isConfirmed && !isFinalised) {
    UIDatabase.write(() => {
      transaction.confirm(UIDatabase);
      UIDatabase.save('Transaction', transaction);
    });
  }

  const navigationAction = NavigationActions.navigate({
    routeName: ROUTES.CUSTOMER_INVOICE,
    params: {
      title: `${navStrings.invoice} ${transaction.serialNumber}`,
      transaction,
      pageObject: transaction,
    },
  });

  dispatch(navigationAction);
};

/**
 * Action creator for navigating to a SupplierInvoice. Ensures the SI is finalised, if
 * confirmed before navigating. This should not occur, but if this is not enforced, a
 * user can reduce the amount of stock in a SI which has been used in a CI, which causes
 * inventory adjustments instantly.
 *
 * @param {Object} transaction The SI to navigate to.
 * @param {Object} dispatch    Redux dispatch method.
 */
export const gotoSupplierInvoice = transaction => dispatch => {
  const { isConfirmed } = transaction;

  // Supplier invoices are `new` or `finalised`. Ensure any `confirmed` invoices are
  // `finalised` before navigating.
  if (isConfirmed) {
    UIDatabase.write(() => {
      transaction.finalise(UIDatabase);
      UIDatabase.save('Transaction', transaction);
    });
  }

  const navigationAction = NavigationActions.navigate({
    routeName: ROUTES.SUPPLIER_INVOICE,
    params: {
      title: `${navStrings.invoice} ${transaction.serialNumber}`,
      transaction,
      pageObject: transaction,
    },
  });

  dispatch(navigationAction);
};

/**
 * Navigate to the SupplierRequisitionPage.
 *
 * @param {Object} requisition  SupplierRequisition to navigate to.
 */
export const gotoSupplierRequisition = requisition =>
  NavigationActions.navigate({
    routeName: ROUTES.SUPPLIER_REQUISITION,
    params: {
      title: `${navStrings.requisition} ${requisition.serialNumber}`,
      requisition,
      pageObject: requisition,
    },
  });

/**
 * Navigate to the CustomerRequisitionPage.
 *
 * @param {Object} requisition  Customer requisition to navigate to.
 */
export const gotoCustomerRequisition = requisition =>
  NavigationActions.navigate({
    routeName: ROUTES.CUSTOMER_REQUISITION,
    params: {
      title: `${navStrings.requisition} ${requisition.serialNumber}`,
      requisition,
      pageObject: requisition,
    },
  });

/**
 * Action creator for creating, and navigating to a Supplier Requsition.
 * Requisition is created by a thunk initially.
 *
 * @param {String} CurrentUser The currently logged in user.
 * @param {Object} requisitionParameters Parameters for the to-be-created object.
 * RequisitionParameters can be any fields in Requisition.js to pass to createRecord.
 */
export const createSupplierRequisition = ({
  currentUser,
  ...requisitionParameters
}) => dispatch => {
  // Fetch this stores custom data to find if this store has customized
  // monthsLeadTime.
  const customData = Settings.get(SETTINGS_KEYS.THIS_STORE_CUSTOM_DATA);

  // CustomData is a stringified JSON object.
  const parsedCustomData = customData ? JSON.parse(customData) : '';

  // Months lead time has an effect on daysToSupply for a requisition.
  const monthsLeadTime = parsedCustomData.monthsLeadTime
    ? Number(customData.monthsLeadTime.data)
    : 0;

  // Create the requisition. If a program was supplied, add items from that
  // program, otherwise just navigate to it.
  let requisition;
  UIDatabase.write(() => {
    requisition = createRecord(UIDatabase, 'Requisition', currentUser, {
      ...requisitionParameters,
      monthsLeadTime,
    });

    if (requisition.program) requisition.addItemsFromProgram(UIDatabase);
  });

  dispatch(gotoSupplierRequisition(requisition));
};

/**
 * Creates a stocktake and replaces the current route in the StackNavigator which
 * would be a StocktakeManagePage, with a StocktakeEditPage, with the newly
 * created Stocktake.
 *
 * @param {Object} StocktakeParameters  Parameters for the stocktake to create.
 */
export const createStocktake = ({ currentUser, stocktakeName, program, itemIds }) => dispatch => {
  let stocktake;

  UIDatabase.write(() => {
    stocktake = createRecord(UIDatabase, 'Stocktake', currentUser, stocktakeName, program);
    if (program) stocktake.addItemsFromProgram(UIDatabase);
    else if (itemIds) stocktake.setItemsByID(UIDatabase, itemIds);
  });

  // If creating a Stocktake with a program or with a list of itemIds, navigate to
  // the StocktakeEditPage. Otherwise, navigate to the StocktakeManagePage.
  return program || itemIds
    ? dispatch(gotoStocktakeEditPage(stocktake))
    : dispatch(gotoStocktakeManagePage(stocktakeName, stocktake));
};

/**
 * Action creator which first creates a customer invoice, and then navigates to it
 * for editing.
 *
 * @param {Object} otherParty     The other party of the invoice (Customer)
 * @param {Object} currentUser    The currently logged in user.
 */
export const createCustomerInvoice = (otherParty, currentUser) => dispatch => {
  let newCustomerInvoice;
  UIDatabase.write(() => {
    newCustomerInvoice = createRecord(UIDatabase, 'CustomerInvoice', otherParty, currentUser);
  });

  dispatch(gotoCustomerInvoice(newCustomerInvoice));
};

/**
 * Action creator which first creates a supplier invoice, and then navigates to it
 * for editing.
 *
 * @param {Object} otherParty     The other party of the invoice (Supplier)
 * @param {Object} currentUser    The currently logged in user.
 */
export const createSupplierInvoice = (otherParty, currentUser) => dispatch => {
  let newCustomerInvoice;
  UIDatabase.write(() => {
    newCustomerInvoice = createRecord(UIDatabase, 'SupplierInvoice', otherParty, currentUser);
  });

  dispatch(gotoSupplierInvoice(newCustomerInvoice));
};

/**
 * Updates a Stocktake with the passed array of itemIDs and navigates to a
 * StocktakeEditPage.
 *
 * @param {Object} stocktake realm Stocktake object
 * @param {Array}  itemIds   Array of item id strings that should be the new
 *                           Items in the stocktake.
 * @param {String} name   Name of the stocktake for updating. Cannot update to remove completely.
 */
export const updateStocktake = (stocktake, itemIds, name = '') => dispatch => {
  UIDatabase.write(() => {
    if (name) stocktake.name = name;

    stocktake.setItemsByID(UIDatabase, itemIds);
    UIDatabase.save('Stocktake', stocktake);
  });

  dispatch(gotoStocktakeEditPage(stocktake));
};
<|endoftext|>


<commit_before>(function (name, context, definition) {
  if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
    module.exports = definition();
  } else if (typeof define === 'function' && typeof define.amd  === 'object') {
    define(function () {
      return definition();
    });
  } else {
    var old = context[name];
    context[name] = definition();
    context[name].noConflict = function(){
      context[name] = old;
      return this;
    };
  }
})('anchor', this, function () {

  function emptyFn(ctx, cb){
    if(typeof cb === 'function')
      cb(null, null);
  }

  function Context(){
    this._events = {};
  }
  Context.prototype.on = function (type, fn){
    this._events[type] = this._events[type] || [];

    if(typeof type !== 'string')
      throw new Error('event type is not defined');
    if(typeof fn !== 'function')
      throw new Error('invalid function');

    this._events[type].push(fn);
    return this;
  };
  Context.prototype.emit = function (type, args){
    if(this._events[type]){
      var stack = this._events[type];
      for(var i = 0, l = stack.length; i<l; i++)
        stack[i].apply(this, args);
    }
    return this;
  };

  function Anchor(scope){
    if(!(this instanceof Anchor))
      return new Anchor(scope);

    scope = scope || {};

    this._scope = scope;
    this._methods = {};
    this._middleware = [];

    var self = this;

    this._scope.use = function(){
      var args = Array.prototype.slice.call(arguments);
      //this refers to scope instance
      //self refers to anchor instance

      var name = null;

      //method name array
      if(Array.isArray(args[0])){
        name = args.shift();
        for(var i = 0, l = name.length; i<l; i++)
          this.use.apply(this, [name[i]].concat(args));
        return this;
      }

      //single method name
      if(typeof args[0] === 'string')
        name = args.shift();

      //no method name, iterate all methods
      if(!name){
        for(name in self._methods)
          this.use.apply(this, [name].concat(args));
        return this;
      }

      //scope var init
      if(!this._middleware)
        this._middleware = {};

      if(!this._middleware[name])
        this._middleware[name] = [];

      for(var i = 0, l = args.length; i<l; i++){
        if(typeof args[i] === 'function')
          this._middleware[name].push(args[i]);
        else
          throw new Error('invalid function');
      }

      return this;
    };
  }

  Anchor.prototype.use = function(){
    var args = Array.prototype.slice.call(arguments);
    var name = null;

    if(Array.isArray(args[0])) {
      name = args.shift();
      for(var i = 0, l = name.length; i<l; i++)
        this.use.apply(this, [name[i]].concat(args));
      return this;
    }

    if(typeof args[0] === 'string')
      name = args.shift();

    for(var i = 0, l = args.length; i<l; i++){
      if(typeof args[i] === 'function')
        this._middleware.push({
          name: name,
          fn: args[i]
        });
      else
        throw new Error('invalid function');
    }
    return this;
  };

  Anchor.prototype.define = function(){
    var args = Array.prototype.slice.call(arguments);

    var name = args[0];
    if(Array.isArray(name)) {
      name = args.shift();
      for(var i = 0, l = name.length; i<l; i++)
        this.define.apply(this, [name[i]].concat(args));
      return this;
    }

    if(typeof name !== 'string')
      throw new Error('method name is not defined');

    var invoke = args.pop();
    if (typeof invoke !== 'function')
      invoke = emptyFn;

    this.use.apply(this, args);

    //filter local middleware
    var middleware = [];
    for(var i = 0, l = this._middleware.length; i<l; i++){
      var _name = this._middleware[i].name;
      if(!_name || _name === name)
        middleware.push(this._middleware[i].fn);
    }

    //define method
    this._methods[name] = {};

    //pipe with local middleware
    var _pipe = [].concat(
      middleware,
      invoke
    );
    //define scope method
    this._scope[name] = function(){
      var args = Array.prototype.slice.call(arguments);

      //this refers to scope instance, not anchor instance
      var self = this;

      var callback = emptyFn;
      if (typeof args[args.length - 1] === 'function')
        callback = args.pop();

      var pipe = _pipe;
      //pipe scope middleware if exists
      if(this._middleware && this._middleware[name])
        pipe = [].concat(
          middleware,
          this._middleware[name],
          invoke
        );

      //context object and next triggerer
      var ctx = new Context();
      ctx.method = name;
      ctx.scope = this;
      ctx.args = args;

      var index = 0;
      function next(){
        //trigger callback if args exist
        if(arguments.length > 0){
          var args = Array.prototype.slice.call(arguments);
          callback.apply(self, args);
          ctx.emit('end', args);
          return;
        }
        index++;
        if(pipe[index]){
          //trigger next
          pipe[index].call(self, ctx, next);
          ctx.emit('next');
        }else{
          //trigger empty callback if no more pipe
          callback.apply(self);
          ctx.emit('end');
        }
      }
      pipe[index].call(self, ctx, next);

      return this;
    };
    return this;
  };

  Anchor.prototype.scope = function(){
    return this._scope;
  };

  return Anchor;
});
<commit_msg>ADD params parser<commit_after>(function (name, context, definition) {
  if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
    module.exports = definition();
  } else if (typeof define === 'function' && typeof define.amd  === 'object') {
    define(function () {
      return definition();
    });
  } else {
    var old = context[name];
    context[name] = definition();
    context[name].noConflict = function(){
      context[name] = old;
      return this;
    };
  }
})('anchor', this, function () {

  function emptyFn(ctx, cb){
    if(typeof cb === 'function')
      cb();
    return true;
  }

  //type checking for params parser
  var types = {

  };
  //params parsing middleware
  function paramsParser(){
    var args = Array.prototype.slice.call(arguments);
    var spec = [];
    var min = 0, max = args.length;
    var i, l;
    for(i = 0, l = args.length; i<l; i++){
      var obj = {};
      var str = args[i];
      var required = str.slice(-1) !== '?';
      obj.required = required;

      if(required)
        min++;
      else
        str = str.slice(0,-1);

      var arg = str.split(':');
      obj.name = arg[0];
      if(arg.length > 1){
        //defined type
        var check = types[arg[1]];
        if(typeof check !== 'function')
          throw new Error('type not exist');
        obj.check = check;
      }else
        obj.check = emptyFn;
      spec.push(obj);
    }
    return function(ctx, next){
      var i, l;
      var args = ctx.args;
      var params = {};
      if(args.length < min)
        return next(new Error('Missing parameters.'));
      var index = 0;
      for(i = 0, l = args.length; i<l; i++){
        var val = args[i];
        while(index < max && !spec[index].check(val) && !spec[index].required){
          index++;
        }
        if(index < max && spec[index].check(val)){
          var def = spec[index];
          params[def.name] = val;
          index++;
        }else{
          return next(new Error('Missing parameters.'));
        }
      }
      ctx.params = params;
      next();
    };
  }

  //Context constructor
  function Context(){
    this._events = {};
  }
  Context.prototype.on = function (type, fn){
    this._events[type] = this._events[type] || [];

    if(typeof type !== 'string')
      throw new Error('event type is not defined');
    if(typeof fn !== 'function')
      throw new Error('invalid function');

    this._events[type].push(fn);
    return this;
  };
  Context.prototype.emit = function (type, args){
    if(this._events[type]){
      var stack = this._events[type];
      for(var i = 0, l = stack.length; i<l; i++)
        stack[i].apply(this, args);
    }
    return this;
  };

  //Anchor constructor
  function Anchor(scope){
    if(!(this instanceof Anchor))
      return new Anchor(scope);

    scope = scope || {};

    this._scope = scope;
    this._methods = {};
    this._middleware = [];

    var self = this;

    this._scope.use = function(){
      var args = Array.prototype.slice.call(arguments);
      //this refers to scope instance
      //self refers to anchor instance

      var name = null, i, l;

      //method name array
      if(Array.isArray(args[0])){
        name = args.shift();
        for(i = 0, l = name.length; i<l; i++)
          this.use.apply(this, [name[i]].concat(args));
        return this;
      }

      //single method name
      if(typeof args[0] === 'string')
        name = args.shift();

      //no method name, iterate all methods
      if(!name){
        for(name in self._methods)
          this.use.apply(this, [name].concat(args));
        return this;
      }

      //scope var init
      if(!this._middleware)
        this._middleware = {};

      if(!this._middleware[name])
        this._middleware[name] = [];

      for(i = 0, l = args.length; i<l; i++){
        if(typeof args[i] === 'function')
          this._middleware[name].push(args[i]);
        else
          throw new Error('invalid function');
      }

      return this;
    };
  }

  Anchor.prototype.use = function(){
    var args = Array.prototype.slice.call(arguments);
    var name = null, i, l;

    if(Array.isArray(args[0])) {
      name = args.shift();
      for(i = 0, l = name.length; i<l; i++)
        this.use.apply(this, [name[i]].concat(args));
      return this;
    }

    if(typeof args[0] === 'string')
      name = args.shift();

    for(i = 0, l = args.length; i<l; i++){
      if(typeof args[i] === 'function')
        this._middleware.push({
          name: name,
          fn: args[i]
        });
      else
        throw new Error('invalid function');
    }
    return this;
  };

  Anchor.prototype.define = function(){
    var args = Array.prototype.slice.call(arguments);
    var i, l;

    var name = args[0];
    if(Array.isArray(name)) {
      name = args.shift();
      for(i = 0, l = name.length; i<l; i++)
        this.define.apply(this, [name[i]].concat(args));
      return this;
    }

    if(typeof name !== 'string')
      throw new Error('method name is not defined');

    var invoke = args.pop();
    if (typeof invoke !== 'function')
      invoke = emptyFn;

    this.use.apply(this, args);

    //filter local middleware
    var middleware = [];
    for(i = 0, l = this._middleware.length; i<l; i++){
      var _name = this._middleware[i].name;
      if(!_name || _name === name)
        middleware.push(this._middleware[i].fn);
    }

    //define method
    this._methods[name] = {};

    //pipe with local middleware
    var _pipe = [].concat(
      middleware,
      invoke
    );
    //define scope method
    this._scope[name] = function(){
      var args = Array.prototype.slice.call(arguments);

      //this refers to scope instance, not anchor instance
      var self = this;

      var callback = emptyFn;
      if (typeof args[args.length - 1] === 'function')
        callback = args.pop();

      var pipe = _pipe;
      //pipe scope middleware if exists
      if(this._middleware && this._middleware[name])
        pipe = [].concat(
          middleware,
          this._middleware[name],
          invoke
        );

      //context object and next triggerer
      var ctx = new Context();
      ctx.method = name;
      ctx.scope = this;
      ctx.args = args;

      var index = 0;
      function next(){
        //trigger callback if args exist
        if(arguments.length > 0){
          var args = Array.prototype.slice.call(arguments);
          callback.apply(self, args);
          ctx.emit('end', args);
          return;
        }
        index++;
        if(pipe[index]){
          //trigger next
          pipe[index].call(self, ctx, next);
          ctx.emit('next');
        }else{
          //trigger empty callback if no more pipe
          callback.apply(self);
          ctx.emit('end');
        }
      }
      pipe[index].call(self, ctx, next);

      return this;
    };
    return this;
  };

  Anchor.prototype.scope = function(){
    return this._scope;
  };
  Anchor.paramsParser = paramsParser;

  return Anchor;
});
<|endoftext|>


<commit_before>'use strict'

const express = require('express');
const { Counter, Histogram, register } = require('prom-client');
const split = require('split2');
const { Transform } = require('stream');
const DEV_ENVS = ['test', 'development'];

const requestCounter = new Counter({
    name: 'maps_api_requests_total',
    help: 'MAPS API requests total'
});

const requestOkCounter = new Counter({
    name: 'maps_api_requests_ok_total',
    help: 'MAPS API requests ok total'
});

const requestErrorCounter = new Counter({
    name: 'maps_api_requests_errors_total',
    help: 'MAPS API requests errors total'
});

const responseTimeHistogram = new Histogram({
    name: 'maps_api_response_time_total',
    help: 'MAPS API response time total'
});

const userRequestCounter = new Counter({
    name: 'maps_api_requests',
    help: 'MAPS API requests per user',
    labelNames: ['user', 'http_code']
});

const userRequestOkCounter = new Counter({
    name: 'maps_api_requests_ok',
    help: 'MAPS API requests per user with success HTTP code',
    labelNames: ['user', 'http_code']
});

const userRequestErrorCounter = new Counter({
    name: 'maps_api_requests_errors',
    help: 'MAPS API requests per user with error HTTP code',
    labelNames: ['user', 'http_code']
});

const userResponseTimeHistogram = new Histogram({
    name: 'maps_api_response_time',
    help: 'MAPS API response time total',
    labelNames: ['user']
});

module.exports = function metricsCollector () {
    return new Transform({
        transform (chunk, enc, callback) {
            let entry;

            try {
                entry = JSON.parse(chunk);
                const { level, time } = entry;

                if (level === undefined && time === undefined) {
                    throw new Error('Entry log is not valid');
                }
            } catch (e) {
                if (DEV_ENVS.includes(process.env.NODE_ENV)) {
                    this.push(chunk);
                }
                return callback();
            }

            const { request, response, stats } = entry;

            if (request === undefined || response === undefined || stats === undefined) {
                this.push(chunk + '\n');
                return callback();
            }

            const { statusCode, headers } = response;
            const { 'carto-user': user } = headers;

            if (statusCode === undefined || headers === undefined || user === undefined) {
                this.push(chunk + '\n');
                return callback();
            }

            requestCounter.inc();
            userRequestCounter.labels(user, `${statusCode}`).inc();

            if (statusCode >= 200 && statusCode < 400) {
                requestOkCounter.inc();
                userRequestOkCounter.labels(user, `${statusCode}`).inc();
            } else {
                requestErrorCounter.inc();
                userRequestErrorCounter.labels(user, `${statusCode}`).inc();
            }

            const { response: responseTime } = stats;

            if (Number.isFinite(responseTime)) {
                responseTimeHistogram.observe(responseTime);
                userResponseTimeHistogram.labels(user).observe(responseTime);
            }

            this.push(chunk);
            callback();
        }
    })
}

const port = process.env.PORT || 9144;
const app = express();

app.get('/metrics', (req, res) => {
    res.set('Content-Type', register.contentType);
    res.end(register.metrics());
});

app.listen(port);
<commit_msg>User native http server<commit_after>'use strict'

const http = require('http');
const { Counter, Histogram, register } = require('prom-client');
const split = require('split2');
const { Transform } = require('stream');
const DEV_ENVS = ['test', 'development'];

const requestCounter = new Counter({
    name: 'maps_api_requests_total',
    help: 'MAPS API requests total'
});

const requestOkCounter = new Counter({
    name: 'maps_api_requests_ok_total',
    help: 'MAPS API requests ok total'
});

const requestErrorCounter = new Counter({
    name: 'maps_api_requests_errors_total',
    help: 'MAPS API requests errors total'
});

const responseTimeHistogram = new Histogram({
    name: 'maps_api_response_time_total',
    help: 'MAPS API response time total'
});

const userRequestCounter = new Counter({
    name: 'maps_api_requests',
    help: 'MAPS API requests per user',
    labelNames: ['user', 'http_code']
});

const userRequestOkCounter = new Counter({
    name: 'maps_api_requests_ok',
    help: 'MAPS API requests per user with success HTTP code',
    labelNames: ['user', 'http_code']
});

const userRequestErrorCounter = new Counter({
    name: 'maps_api_requests_errors',
    help: 'MAPS API requests per user with error HTTP code',
    labelNames: ['user', 'http_code']
});

const userResponseTimeHistogram = new Histogram({
    name: 'maps_api_response_time',
    help: 'MAPS API response time total',
    labelNames: ['user']
});

module.exports = function metricsCollector () {
    return new Transform({
        transform (chunk, enc, callback) {
            let entry;

            try {
                entry = JSON.parse(chunk);
                const { level, time } = entry;

                if (level === undefined && time === undefined) {
                    throw new Error('Entry log is not valid');
                }
            } catch (e) {
                if (DEV_ENVS.includes(process.env.NODE_ENV)) {
                    this.push(chunk);
                }
                return callback();
            }

            const { request, response, stats } = entry;

            if (request === undefined || response === undefined || stats === undefined) {
                this.push(chunk + '\n');
                return callback();
            }

            const { statusCode, headers } = response;
            const { 'carto-user': user } = headers;

            if (statusCode === undefined || headers === undefined || user === undefined) {
                this.push(chunk + '\n');
                return callback();
            }

            requestCounter.inc();
            userRequestCounter.labels(user, `${statusCode}`).inc();

            if (statusCode >= 200 && statusCode < 400) {
                requestOkCounter.inc();
                userRequestOkCounter.labels(user, `${statusCode}`).inc();
            } else {
                requestErrorCounter.inc();
                userRequestErrorCounter.labels(user, `${statusCode}`).inc();
            }

            const { response: responseTime } = stats;

            if (Number.isFinite(responseTime)) {
                responseTimeHistogram.observe(responseTime);
                userResponseTimeHistogram.labels(user).observe(responseTime);
            }

            this.push(chunk);
            callback();
        }
    })
}

const port = process.env.PORT || 9144;

http
    .createServer((req, res) => {
        res.writeHead(200, { 'Content-Type': register.contentType });
        res.end(register.metrics());
    })
    .listen(port);
<|endoftext|>


<commit_before>////////////////////////////////////////////////////////////////////////////////
// Atom will evaluate this file each time a new window is opened. It is run
// after packages are loaded/activated and after the previous editor state
// has been restored.
////////////////////////////////////////////////////////////////////////////////

// Toggle between light and dark theme.
atom.commands.add('atom-workspace', 'me:toggle-theme', function () {
  const activeThemes = atom.themes.getActiveThemeNames()

  if (activeThemes[0].indexOf('light') > 0) {
    atom.config.set('core.themes', ['nucleus-dark-ui', 'one-dark-syntax'])
  } else {
    atom.config.set('core.themes', ['one-light-ui', 'one-light-syntax'])
  }
})

// Close all panes.
//
// Atom's built-in 'pane:close-other-items' is super handy. But sometimes you
// just really want a completely clean slate.
//
// "Shut down all the [panes] on the detention level! ... Shut them all down.
// Hurry!"
atom.commands.add('atom-workspace', 'me:close-all-panes', () =>
  atom.workspace.getPanes().forEach(pane => pane.destroy())
)

// If the right dock is focused, close it. If the right dock is not focused,
// display the right dock and focus it.
//
// I bind this command a keyboard shortcut so that I can use a single keyboard
// shortcut to go straight to the Git tab from anywhere inside Atom, and then use
// that same keyboard shortcut to close the Git tab when I'm done committing my
// changes.
atom.commands.add('atom-workspace', 'me:focus-or-close-right-dock', function () {
  const workspaceActivePaneItem = atom.workspace.getActivePaneItem()
  const rightDockActivePaneItem = atom.workspace.getRightDock().getActivePaneItem()
  const isRightDockFocused = workspaceActivePaneItem === rightDockActivePaneItem

  if (isRightDockFocused) {
    atom.commands.dispatch(atom.workspace.getElement(), 'window:toggle-right-dock')
  } else {
    atom.workspace.getRightDock().activate()
  }
})

// Save all open buffers, even if they're not modified. This behavior is useful
// when you're using a file watcher to trigger scripts (e.g., using guard to
// run your Ruby tests).
//
// Atom's built-in "window:save-all" command previously provided this behavior,
// but it was removed in https://github.com/atom/atom/pull/9249.
atom.commands.add('atom-workspace', 'me:save-all', () =>
  atom.workspace.getPanes().forEach(pane => pane.getItems().map((item) => pane.saveItem(item)))
)

// Generate ctags.
atom.commands.add('atom-text-editor', 'me:generate-ctags', () => {
  let child
  const sys = require('sys')
  const { exec } = require('child_process')

  const tag_command = `    \
    /usr/local/bin/ctags   \
    --exclude=log          \
    --exclude=node_modules \
    --exclude=tmp          \
    --exclude=vendor       \
    --tag-relative -R      \
    `

  atom.notifications.addInfo('Generating ctags')
  atom.project.getPaths().map((path) =>
    (child = exec(tag_command, {cwd: path}, function (error, stdout, stderr) {
      if (error) {
        atom.notifications.addError('ctags exited with error',
          {detail: error, dismissable: true}
        )
      } else if (stderr) {
        atom.notifications.addWarning('Generated ctags (with warnings)',
          {detail: stderr, dismissable: true}
        )
      } else {
        atom.notifications.addSuccess('Finished generating ctags',
          {detail: stdout}
        )
      }
    })
  ))
})

// Define commands that should only be available in an editor view (and should
// not be available in a tree view, for example).
atom.commands.add('atom-text-editor', {
  // Approximate Vim's "H" motion: Move the cursor to the topmost line that is
  // currently visible.
  'me:move-to-top-visible-line' () {
    const editor = atom.workspace.getActiveTextEditor()
    let topRow = editor.getFirstVisibleScreenRow()
    if (topRow !== 0) { topRow = topRow + 1 }
    editor.setCursorBufferPosition([topRow, 0], {autoscroll: false})
  },

  // Approximate Vim's "M" motion: Move the cursor to the middle line that is
  // currently visible.
  'me:move-to-middle-visible-line' () {
    const editor = atom.workspace.getActiveTextEditor()
    const topRow = editor.getFirstVisibleScreenRow()
    const bottomRow = editor.getLastVisibleScreenRow()
    const middleRow = Math.floor((bottomRow - topRow) / 2) + topRow
    editor.setCursorBufferPosition([middleRow, 0])
  },

  // Approximate Vim's "L" motion: Move the cursor to the bottommost line that
  // is currently visible.
  'me:move-to-bottom-visible-line' () {
    const editor = atom.workspace.getActiveTextEditor()
    let bottomRow = editor.getLastVisibleScreenRow()
    if (bottomRow !== editor.getLastScreenRow()) { bottomRow = bottomRow - 1 }
    editor.setCursorBufferPosition([bottomRow, 0], {autoscroll: false})
  },

  // Approximate Vim's "zt" motion: Scroll the view such that the line containing
  // the cursor is at the top of the view.
  'me:scroll-cursor-to-top' () {
    const textEditor = atom.workspace.getActiveTextEditor()
    const textEditorElement = atom.views.getView(textEditor)
    const cursorPosition = textEditor.getCursorScreenPosition()
    const pixelPositionForCursorPosition =
      textEditorElement.pixelPositionForScreenPosition(cursorPosition)
    textEditorElement.setScrollTop(pixelPositionForCursorPosition.top)
  },

  // Approximate Vim's "zz" motion: Scroll the view such that the line containing
  // the cursor is vertically centered in the view.
  'me:scroll-cursor-to-center' () {
    const textEditor = atom.workspace.getActiveTextEditor()
    const textEditorElement = atom.views.getView(textEditor)
    const cursorPosition = textEditor.getCursorScreenPosition()
    const pixelPositionForCursorPosition =
      textEditorElement.pixelPositionForScreenPosition(cursorPosition)
    const halfScreenHeight = textEditorElement.getHeight() / 2
    const scrollTop = pixelPositionForCursorPosition.top - halfScreenHeight
    textEditorElement.setScrollTop(scrollTop)
  },

  // Approximate Vim's "zb" motion: Scroll the view such that the line containing
  // the cursor is at the bottom of the view.
  'me:scroll-cursor-to-bottom' () {
    const textEditor = atom.workspace.getActiveTextEditor()
    const textEditorElement = atom.views.getView(textEditor)
    const cursorPosition = textEditor.getCursorScreenPosition()
    const scrollBottomPosition = [cursorPosition.row + 1, cursorPosition.column]
    const pixelPositionForScrollBottomPosition =
      textEditorElement.pixelPositionForScreenPosition(scrollBottomPosition)
    textEditorElement.setScrollBottom(pixelPositionForScrollBottomPosition.top)
  },

  // Move the cursor to the next diff in the editor, and then scroll the view
  // such that the line containing the cursor is vertically centered in the view.
  'me:center-next-diff' () {
    const editor = atom.workspace.getActiveTextEditor()
    const editorView = atom.views.getView(editor)
    atom.commands.dispatch(editorView, 'git-diff:move-to-next-diff')
    atom.commands.dispatch(editorView, 'me:scroll-cursor-to-center')
  },

  // Move the cursor to the previous diff in the editor, and then scroll the view
  // such that the line containing the cursor is vertically centered in the view.
  'me:center-previous-diff' () {
    const editor = atom.workspace.getActiveTextEditor()
    const editorView = atom.views.getView(editor)
    atom.commands.dispatch(editorView, 'git-diff:move-to-previous-diff')
    atom.commands.dispatch(editorView, 'me:scroll-cursor-to-center')
  }
})

function focusTracer (event) {
  console.log('window.focus =', event.target)
}

atom.commands.add('atom-workspace', {
  // Log each time focus changes to a new element in Atom's UI.
  'me:trace-focus' (event) {
    window.addEventListener('focusin', focusTracer)
  },

  // Stop logging each time focus changes to a new element in Atom's UI.
  'me:untrace-focus' (event) {
    window.removeEventListener('focusin', focusTracer)
  }
})
<commit_msg>:atom: Add Atom command for sharing portal & copying its URL in one step<commit_after>////////////////////////////////////////////////////////////////////////////////
// Atom will evaluate this file each time a new window is opened. It is run
// after packages are loaded/activated and after the previous editor state
// has been restored.
////////////////////////////////////////////////////////////////////////////////

// Toggle between light and dark theme.
atom.commands.add('atom-workspace', 'me:toggle-theme', function () {
  const activeThemes = atom.themes.getActiveThemeNames()

  if (activeThemes[0].indexOf('light') > 0) {
    atom.config.set('core.themes', ['nucleus-dark-ui', 'one-dark-syntax'])
  } else {
    atom.config.set('core.themes', ['one-light-ui', 'one-light-syntax'])
  }
})

// Close all panes.
//
// Atom's built-in 'pane:close-other-items' is super handy. But sometimes you
// just really want a completely clean slate.
//
// "Shut down all the [panes] on the detention level! ... Shut them all down.
// Hurry!"
atom.commands.add('atom-workspace', 'me:close-all-panes', () =>
  atom.workspace.getPanes().forEach(pane => pane.destroy())
)

// Share a Teletype portal to the workspace and copy the portal URL to the
// clipboard for easy sharing.
atom.commands.add('atom-workspace', 'me:share-portal-and-copy-url', async () => {
  const teletype = atom.packages.getLoadedPackage('teletype').mainModule
  await teletype.sharePortal()
  atom.commands.dispatch(atom.workspace.getElement(), 'teletype:copy-portal-url')
})

// If the right dock is focused, close it. If the right dock is not focused,
// display the right dock and focus it.
//
// I bind this command a keyboard shortcut so that I can use a single keyboard
// shortcut to go straight to the Git tab from anywhere inside Atom, and then use
// that same keyboard shortcut to close the Git tab when I'm done committing my
// changes.
atom.commands.add('atom-workspace', 'me:focus-or-close-right-dock', function () {
  const workspaceActivePaneItem = atom.workspace.getActivePaneItem()
  const rightDockActivePaneItem = atom.workspace.getRightDock().getActivePaneItem()
  const isRightDockFocused = workspaceActivePaneItem === rightDockActivePaneItem

  if (isRightDockFocused) {
    atom.commands.dispatch(atom.workspace.getElement(), 'window:toggle-right-dock')
  } else {
    atom.workspace.getRightDock().activate()
  }
})

// Save all open buffers, even if they're not modified. This behavior is useful
// when you're using a file watcher to trigger scripts (e.g., using guard to
// run your Ruby tests).
//
// Atom's built-in "window:save-all" command previously provided this behavior,
// but it was removed in https://github.com/atom/atom/pull/9249.
atom.commands.add('atom-workspace', 'me:save-all', () =>
  atom.workspace.getPanes().forEach(pane => pane.getItems().map((item) => pane.saveItem(item)))
)

// Generate ctags.
atom.commands.add('atom-text-editor', 'me:generate-ctags', () => {
  let child
  const sys = require('sys')
  const { exec } = require('child_process')

  const tag_command = `    \
    /usr/local/bin/ctags   \
    --exclude=log          \
    --exclude=node_modules \
    --exclude=tmp          \
    --exclude=vendor       \
    --tag-relative -R      \
    `

  atom.notifications.addInfo('Generating ctags')
  atom.project.getPaths().map((path) =>
    (child = exec(tag_command, {cwd: path}, function (error, stdout, stderr) {
      if (error) {
        atom.notifications.addError('ctags exited with error',
          {detail: error, dismissable: true}
        )
      } else if (stderr) {
        atom.notifications.addWarning('Generated ctags (with warnings)',
          {detail: stderr, dismissable: true}
        )
      } else {
        atom.notifications.addSuccess('Finished generating ctags',
          {detail: stdout}
        )
      }
    })
  ))
})

// Define commands that should only be available in an editor view (and should
// not be available in a tree view, for example).
atom.commands.add('atom-text-editor', {
  // Approximate Vim's "H" motion: Move the cursor to the topmost line that is
  // currently visible.
  'me:move-to-top-visible-line' () {
    const editor = atom.workspace.getActiveTextEditor()
    let topRow = editor.getFirstVisibleScreenRow()
    if (topRow !== 0) { topRow = topRow + 1 }
    editor.setCursorBufferPosition([topRow, 0], {autoscroll: false})
  },

  // Approximate Vim's "M" motion: Move the cursor to the middle line that is
  // currently visible.
  'me:move-to-middle-visible-line' () {
    const editor = atom.workspace.getActiveTextEditor()
    const topRow = editor.getFirstVisibleScreenRow()
    const bottomRow = editor.getLastVisibleScreenRow()
    const middleRow = Math.floor((bottomRow - topRow) / 2) + topRow
    editor.setCursorBufferPosition([middleRow, 0])
  },

  // Approximate Vim's "L" motion: Move the cursor to the bottommost line that
  // is currently visible.
  'me:move-to-bottom-visible-line' () {
    const editor = atom.workspace.getActiveTextEditor()
    let bottomRow = editor.getLastVisibleScreenRow()
    if (bottomRow !== editor.getLastScreenRow()) { bottomRow = bottomRow - 1 }
    editor.setCursorBufferPosition([bottomRow, 0], {autoscroll: false})
  },

  // Approximate Vim's "zt" motion: Scroll the view such that the line containing
  // the cursor is at the top of the view.
  'me:scroll-cursor-to-top' () {
    const textEditor = atom.workspace.getActiveTextEditor()
    const textEditorElement = atom.views.getView(textEditor)
    const cursorPosition = textEditor.getCursorScreenPosition()
    const pixelPositionForCursorPosition =
      textEditorElement.pixelPositionForScreenPosition(cursorPosition)
    textEditorElement.setScrollTop(pixelPositionForCursorPosition.top)
  },

  // Approximate Vim's "zz" motion: Scroll the view such that the line containing
  // the cursor is vertically centered in the view.
  'me:scroll-cursor-to-center' () {
    const textEditor = atom.workspace.getActiveTextEditor()
    const textEditorElement = atom.views.getView(textEditor)
    const cursorPosition = textEditor.getCursorScreenPosition()
    const pixelPositionForCursorPosition =
      textEditorElement.pixelPositionForScreenPosition(cursorPosition)
    const halfScreenHeight = textEditorElement.getHeight() / 2
    const scrollTop = pixelPositionForCursorPosition.top - halfScreenHeight
    textEditorElement.setScrollTop(scrollTop)
  },

  // Approximate Vim's "zb" motion: Scroll the view such that the line containing
  // the cursor is at the bottom of the view.
  'me:scroll-cursor-to-bottom' () {
    const textEditor = atom.workspace.getActiveTextEditor()
    const textEditorElement = atom.views.getView(textEditor)
    const cursorPosition = textEditor.getCursorScreenPosition()
    const scrollBottomPosition = [cursorPosition.row + 1, cursorPosition.column]
    const pixelPositionForScrollBottomPosition =
      textEditorElement.pixelPositionForScreenPosition(scrollBottomPosition)
    textEditorElement.setScrollBottom(pixelPositionForScrollBottomPosition.top)
  },

  // Move the cursor to the next diff in the editor, and then scroll the view
  // such that the line containing the cursor is vertically centered in the view.
  'me:center-next-diff' () {
    const editor = atom.workspace.getActiveTextEditor()
    const editorView = atom.views.getView(editor)
    atom.commands.dispatch(editorView, 'git-diff:move-to-next-diff')
    atom.commands.dispatch(editorView, 'me:scroll-cursor-to-center')
  },

  // Move the cursor to the previous diff in the editor, and then scroll the view
  // such that the line containing the cursor is vertically centered in the view.
  'me:center-previous-diff' () {
    const editor = atom.workspace.getActiveTextEditor()
    const editorView = atom.views.getView(editor)
    atom.commands.dispatch(editorView, 'git-diff:move-to-previous-diff')
    atom.commands.dispatch(editorView, 'me:scroll-cursor-to-center')
  }
})

function focusTracer (event) {
  console.log('window.focus =', event.target)
}

atom.commands.add('atom-workspace', {
  // Log each time focus changes to a new element in Atom's UI.
  'me:trace-focus' (event) {
    window.addEventListener('focusin', focusTracer)
  },

  // Stop logging each time focus changes to a new element in Atom's UI.
  'me:untrace-focus' (event) {
    window.removeEventListener('focusin', focusTracer)
  }
})
<|endoftext|>


<commit_before>import { expect } from 'chai';
import Ajv from 'ajv';
import schema from 'vets-json-schema/dist/22-1990-schema.json';
import qc from 'quick_check';
import _ from 'lodash';

import {
  states
} from '../../../../src/js/common/utils/options-for-select';

import {
  createVeteran,
  veteranToApplication,
  createTour,
  createEducationPeriod,
  createRotcScholarship,
  createEmploymentPeriod
} from '../../../../src/js/edu-benefits/1990/utils/veteran';

import {
  relinquishableBenefits,
  contactOptions,
  schoolTypes,
  accountTypes,
  suffixes,
  serviceBranches,
  hoursTypes,
  ownBenefitsOptions
} from '../../../../src/js/edu-benefits/1990/utils/options-for-select';

const ajv = new Ajv();
const validate = ajv.compile(schema);
const makeField = gen => qc.objectLike({
  dirty: true,
  value: gen
});
const yesNoGen = () => qc.choose('Y', 'N', '');
const routingNumbers = [
  '114923756',
  '122105045',
  '265473812',
  '211174123'
];
const dateGen = () => qc.objectLike({
  month: makeField(qc.int.between(1, 12)),
  day: makeField(qc.int.between(1, 28)),
  year: makeField(qc.int.between(1900, 2016)),
});
const nameGen = () => qc.objectLike({
  first: makeField(qc.string.matching(/^.{4,30}$/)),
  middle: makeField(qc.string.matching(/^.{4,30}$/)),
  last: makeField(qc.string.matching(/^.{4,30}$/)),
  suffix: makeField(qc.choose(...suffixes)),
});
const addressGen = () => qc.objectLike({
  street: makeField(qc.string.matching(/^.{4,50}$/)),
  city: makeField(qc.string.matching(/^.{4,50}$/)),
  country: makeField('USA'),
  state: makeField(qc.choose(...states.USA.map(x => x.value))),
  postalCode: makeField(qc.string.matching(/^(\d{5}|\d{9})$/)),
});
const tourOfDutyGen = () => qc.objectLike({
  dateRange: qc.objectLike({
    to: dateGen(),
    from: dateGen()
  }),
  serviceBranch: makeField(qc.choose(...serviceBranches.map(x => x.value))),
  serviceStatus: makeField(qc.string),
  applyPeriodToSelected: qc.bool,
  benefitsToApplyTo: makeField(qc.string)
});
const scholarshipGen = () => qc.objectLike({
  amount: makeField(qc.string.matching(/^[$]{0,1}\d{1,5}\.\d{1,2}$/)),
  year: makeField(qc.int.between(1900, 2016))
});
const educationGen = () => qc.objectLike({
  name: makeField(qc.string),
  city: makeField(qc.string),
  state: makeField(qc.choose(...states.USA.map(x => x.value))),
  dateRange: qc.objectLike({
    to: dateGen(),
    from: dateGen()
  }),
  hours: makeField(qc.int.between(1, 500)),
  hoursType: makeField(qc.choose(...hoursTypes.map(x => x.value))),
  degreeReceived: makeField(qc.string),
  major: makeField(qc.string)
});
const employmentGen = () => qc.objectLike({
  name: makeField(qc.string),
  months: makeField(qc.int.between(1, 600)),
  licenseOrRating: makeField(qc.string),
  postMilitaryJob: makeField(qc.string)
});
const matches = (source, target) => {
  if (!_.isUndefined(source) && !_.isUndefined(target)) {
    if (_.isObject(source)) {
      return Object.keys(source).every(key => matches(source[key], target[key]));
    }

    return true;
  }

  return false;
};

function createTestVeteran() {
  return {
    benefitsRelinquished: makeField(qc.choose(...relinquishableBenefits.map(x => x.value))),
    chapter30: qc.bool,
    chapter1606: true,
    chapter32: qc.bool,
    chapter33: qc.bool,
    checkedBenefit: makeField(''),
    serviceAcademyGraduationYear: makeField(qc.int.between(1900, 2016)),
    currentlyActiveDuty: qc.objectLike({
      yes: makeField(yesNoGen()),
      onTerminalLeave: makeField(yesNoGen()),
      nonVaAssistance: makeField(yesNoGen())
    }),
    toursOfDuty: qc.arrayOf(tourOfDutyGen()),
    postHighSchoolTrainings: qc.arrayOf(educationGen()),
    faaFlightCertificatesInformation: makeField(qc.string),
    highSchoolOrGedCompletionDate: dateGen(),
    seniorRotcCommissioned: makeField(yesNoGen()),
    seniorRotc: qc.objectLike({
      commissionYear: makeField(qc.int.between(1900, 2016)),
      rotcScholarshipAmounts: qc.arrayOf(scholarshipGen())
    }),
    seniorRotcScholarshipProgram: makeField(yesNoGen()),
    civilianBenefitsAssistance: qc.bool,
    additionalContributions: qc.bool,
    activeDutyKicker: qc.bool,
    reserveKicker: qc.bool,
    activeDutyRepaying: makeField(yesNoGen()),
    activeDutyRepayingPeriod: qc.objectLike({
      to: dateGen(),
      from: dateGen()
    }),
    serviceBefore1977: qc.objectLike({
      married: makeField(yesNoGen()),
      haveDependents: makeField(yesNoGen()),
      parentDependent: makeField(yesNoGen())
    }),
    veteranFullName: nameGen(),
    veteranSocialSecurityNumber: makeField(qc.except(qc.string.matching(/^\d{9}$/), '123456789')),
    veteranDateOfBirth: dateGen(),
    gender: makeField(qc.choose('M', 'F', '')),
    hasNonMilitaryJobs: makeField(yesNoGen()),
    nonMilitaryJobs: qc.arrayOf(employmentGen()),
    veteranAddress: addressGen(),
    email: makeField('test@test.com'),
    emailConfirmation: makeField('test@test.com'),
    homePhone: makeField(qc.string.matching(/^\d{10}$/)),
    mobilePhone: makeField(qc.string.matching(/^\d{10}$/)),
    preferredContactMethod: makeField(qc.choose(...contactOptions.map(x => x.value))),
    educationType: makeField(qc.choose(...schoolTypes.map(x => x.value))),
    school: qc.objectLike({
      name: makeField(qc.string),
      address: addressGen()
    }),
    educationObjective: makeField(qc.string),
    educationStartDate: dateGen(),
    secondaryContact: qc.objectLike({
      fullName: makeField(qc.string),
      sameAddress: qc.bool,
      address: addressGen(),
      phone: makeField(qc.string.matching(/^\d{10}$/))
    }),
    bankAccount: qc.objectLike({
      accountType: makeField(qc.choose(...accountTypes.map(x => x.value))),
      accountNumber: makeField(qc.string),
      routingNumber: makeField(qc.choose(...routingNumbers))
    }),
    applyingUsingOwnBenefits: makeField(qc.choose(...ownBenefitsOptions.map(x => x.value))),
    benefitsRelinquishedDate: dateGen(),
    privacyAgreementAccepted: true
  };
}

describe('Edu benefits json schema', () => {
  it.only('should have matching test veteran and blank veteran shape', () => {
    console.log(schema); // eslint-disable-line
    const testForm = qc.objectLike(createTestVeteran())(1);
    const blankForm = createVeteran();

    expect(matches(blankForm, testForm)).to.be.true;
  });
  describe('should have matching test veteran and blank veteran shape for', () => {
    it('toursOfDuty', () => {
      const testForm = tourOfDutyGen()(1);
      const blankForm = createTour();

      expect(matches(blankForm, testForm)).to.be.true;
    });
    it('postHighSchoolTrainings', () => {
      const testForm = educationGen()(1);
      const blankForm = createEducationPeriod();

      expect(matches(blankForm, testForm)).to.be.true;
    });
    it('nonMilitaryJobs', () => {
      const testForm = employmentGen()(1);
      const blankForm = createEmploymentPeriod();

      expect(matches(blankForm, testForm)).to.be.true;
    });
    it('rotcScholarshipAmounts', () => {
      const testForm = scholarshipGen()(1);
      const blankForm = createRotcScholarship();

      expect(matches(blankForm, testForm)).to.be.true;
    });
  });
  it('should not validate a blank veteran form', () => {
    const application = JSON.parse(veteranToApplication(createVeteran()));
    const result = validate(application);
    expect(result).to.be.false;
  });
  it('should validate with minimum required fields', () => {
    const form = createVeteran();

    form.veteranFullName.first = 'Jane';
    form.veteranFullName.last = 'Testing';
    form.veteranAddress.street = '123 Test st';
    form.veteranAddress.country = 'USA';
    form.veteranAddress.state = 'VT';
    form.veteranAddress.city = 'Somewhere';
    form.veteranAddress.postalCode = '12345';
    form.privacyAgreementAccepted = true;

    const application = JSON.parse(veteranToApplication(form));
    const result = validate(application);
    expect(result).to.be.true;
  });
  it('should pass validation with valid generated data', function schemaTest() {
    this.timeout(10000);
    qc.forAll(qc.objectLike(createTestVeteran()), (vetForm) => {
      const application = JSON.parse(veteranToApplication(vetForm));
      const result = validate(application);

      if (!result) {
        /* eslint-disable no-console */
        console.log(JSON.stringify(application, null, 2));
        console.log(validate.errors);
        /* eslint-enable no-console */
      }
      expect(result).to.be.true;
    });
  });
});
<commit_msg>Remove console.log statement that is no longer needed<commit_after>import { expect } from 'chai';
import Ajv from 'ajv';
import schema from 'vets-json-schema/dist/22-1990-schema.json';
import qc from 'quick_check';
import _ from 'lodash';

import {
  states
} from '../../../../src/js/common/utils/options-for-select';

import {
  createVeteran,
  veteranToApplication,
  createTour,
  createEducationPeriod,
  createRotcScholarship,
  createEmploymentPeriod
} from '../../../../src/js/edu-benefits/1990/utils/veteran';

import {
  relinquishableBenefits,
  contactOptions,
  schoolTypes,
  accountTypes,
  suffixes,
  serviceBranches,
  hoursTypes,
  ownBenefitsOptions
} from '../../../../src/js/edu-benefits/1990/utils/options-for-select';

const ajv = new Ajv();
const validate = ajv.compile(schema);
const makeField = gen => qc.objectLike({
  dirty: true,
  value: gen
});
const yesNoGen = () => qc.choose('Y', 'N', '');
const routingNumbers = [
  '114923756',
  '122105045',
  '265473812',
  '211174123'
];
const dateGen = () => qc.objectLike({
  month: makeField(qc.int.between(1, 12)),
  day: makeField(qc.int.between(1, 28)),
  year: makeField(qc.int.between(1900, 2016)),
});
const nameGen = () => qc.objectLike({
  first: makeField(qc.string.matching(/^.{4,30}$/)),
  middle: makeField(qc.string.matching(/^.{4,30}$/)),
  last: makeField(qc.string.matching(/^.{4,30}$/)),
  suffix: makeField(qc.choose(...suffixes)),
});
const addressGen = () => qc.objectLike({
  street: makeField(qc.string.matching(/^.{4,50}$/)),
  city: makeField(qc.string.matching(/^.{4,50}$/)),
  country: makeField('USA'),
  state: makeField(qc.choose(...states.USA.map(x => x.value))),
  postalCode: makeField(qc.string.matching(/^(\d{5}|\d{9})$/)),
});
const tourOfDutyGen = () => qc.objectLike({
  dateRange: qc.objectLike({
    to: dateGen(),
    from: dateGen()
  }),
  serviceBranch: makeField(qc.choose(...serviceBranches.map(x => x.value))),
  serviceStatus: makeField(qc.string),
  applyPeriodToSelected: qc.bool,
  benefitsToApplyTo: makeField(qc.string)
});
const scholarshipGen = () => qc.objectLike({
  amount: makeField(qc.string.matching(/^[$]{0,1}\d{1,5}\.\d{1,2}$/)),
  year: makeField(qc.int.between(1900, 2016))
});
const educationGen = () => qc.objectLike({
  name: makeField(qc.string),
  city: makeField(qc.string),
  state: makeField(qc.choose(...states.USA.map(x => x.value))),
  dateRange: qc.objectLike({
    to: dateGen(),
    from: dateGen()
  }),
  hours: makeField(qc.int.between(1, 500)),
  hoursType: makeField(qc.choose(...hoursTypes.map(x => x.value))),
  degreeReceived: makeField(qc.string),
  major: makeField(qc.string)
});
const employmentGen = () => qc.objectLike({
  name: makeField(qc.string),
  months: makeField(qc.int.between(1, 600)),
  licenseOrRating: makeField(qc.string),
  postMilitaryJob: makeField(qc.string)
});
const matches = (source, target) => {
  if (!_.isUndefined(source) && !_.isUndefined(target)) {
    if (_.isObject(source)) {
      return Object.keys(source).every(key => matches(source[key], target[key]));
    }

    return true;
  }

  return false;
};

function createTestVeteran() {
  return {
    benefitsRelinquished: makeField(qc.choose(...relinquishableBenefits.map(x => x.value))),
    chapter30: qc.bool,
    chapter1606: true,
    chapter32: qc.bool,
    chapter33: qc.bool,
    checkedBenefit: makeField(''),
    serviceAcademyGraduationYear: makeField(qc.int.between(1900, 2016)),
    currentlyActiveDuty: qc.objectLike({
      yes: makeField(yesNoGen()),
      onTerminalLeave: makeField(yesNoGen()),
      nonVaAssistance: makeField(yesNoGen())
    }),
    toursOfDuty: qc.arrayOf(tourOfDutyGen()),
    postHighSchoolTrainings: qc.arrayOf(educationGen()),
    faaFlightCertificatesInformation: makeField(qc.string),
    highSchoolOrGedCompletionDate: dateGen(),
    seniorRotcCommissioned: makeField(yesNoGen()),
    seniorRotc: qc.objectLike({
      commissionYear: makeField(qc.int.between(1900, 2016)),
      rotcScholarshipAmounts: qc.arrayOf(scholarshipGen())
    }),
    seniorRotcScholarshipProgram: makeField(yesNoGen()),
    civilianBenefitsAssistance: qc.bool,
    additionalContributions: qc.bool,
    activeDutyKicker: qc.bool,
    reserveKicker: qc.bool,
    activeDutyRepaying: makeField(yesNoGen()),
    activeDutyRepayingPeriod: qc.objectLike({
      to: dateGen(),
      from: dateGen()
    }),
    serviceBefore1977: qc.objectLike({
      married: makeField(yesNoGen()),
      haveDependents: makeField(yesNoGen()),
      parentDependent: makeField(yesNoGen())
    }),
    veteranFullName: nameGen(),
    veteranSocialSecurityNumber: makeField(qc.except(qc.string.matching(/^\d{9}$/), '123456789')),
    veteranDateOfBirth: dateGen(),
    gender: makeField(qc.choose('M', 'F', '')),
    hasNonMilitaryJobs: makeField(yesNoGen()),
    nonMilitaryJobs: qc.arrayOf(employmentGen()),
    veteranAddress: addressGen(),
    email: makeField('test@test.com'),
    emailConfirmation: makeField('test@test.com'),
    homePhone: makeField(qc.string.matching(/^\d{10}$/)),
    mobilePhone: makeField(qc.string.matching(/^\d{10}$/)),
    preferredContactMethod: makeField(qc.choose(...contactOptions.map(x => x.value))),
    educationType: makeField(qc.choose(...schoolTypes.map(x => x.value))),
    school: qc.objectLike({
      name: makeField(qc.string),
      address: addressGen()
    }),
    educationObjective: makeField(qc.string),
    educationStartDate: dateGen(),
    secondaryContact: qc.objectLike({
      fullName: makeField(qc.string),
      sameAddress: qc.bool,
      address: addressGen(),
      phone: makeField(qc.string.matching(/^\d{10}$/))
    }),
    bankAccount: qc.objectLike({
      accountType: makeField(qc.choose(...accountTypes.map(x => x.value))),
      accountNumber: makeField(qc.string),
      routingNumber: makeField(qc.choose(...routingNumbers))
    }),
    applyingUsingOwnBenefits: makeField(qc.choose(...ownBenefitsOptions.map(x => x.value))),
    benefitsRelinquishedDate: dateGen(),
    privacyAgreementAccepted: true
  };
}

describe('Edu benefits json schema', () => {
  it('should have matching test veteran and blank veteran shape', () => {
    const testForm = qc.objectLike(createTestVeteran())(1);
    const blankForm = createVeteran();

    expect(matches(blankForm, testForm)).to.be.true;
  });
  describe('should have matching test veteran and blank veteran shape for', () => {
    it('toursOfDuty', () => {
      const testForm = tourOfDutyGen()(1);
      const blankForm = createTour();

      expect(matches(blankForm, testForm)).to.be.true;
    });
    it('postHighSchoolTrainings', () => {
      const testForm = educationGen()(1);
      const blankForm = createEducationPeriod();

      expect(matches(blankForm, testForm)).to.be.true;
    });
    it('nonMilitaryJobs', () => {
      const testForm = employmentGen()(1);
      const blankForm = createEmploymentPeriod();

      expect(matches(blankForm, testForm)).to.be.true;
    });
    it('rotcScholarshipAmounts', () => {
      const testForm = scholarshipGen()(1);
      const blankForm = createRotcScholarship();

      expect(matches(blankForm, testForm)).to.be.true;
    });
  });
  it('should not validate a blank veteran form', () => {
    const application = JSON.parse(veteranToApplication(createVeteran()));
    const result = validate(application);
    expect(result).to.be.false;
  });
  it('should validate with minimum required fields', () => {
    const form = createVeteran();

    form.veteranFullName.first = 'Jane';
    form.veteranFullName.last = 'Testing';
    form.veteranAddress.street = '123 Test st';
    form.veteranAddress.country = 'USA';
    form.veteranAddress.state = 'VT';
    form.veteranAddress.city = 'Somewhere';
    form.veteranAddress.postalCode = '12345';
    form.privacyAgreementAccepted = true;

    const application = JSON.parse(veteranToApplication(form));
    const result = validate(application);
    expect(result).to.be.true;
  });
  it('should pass validation with valid generated data', function schemaTest() {
    this.timeout(10000);
    qc.forAll(qc.objectLike(createTestVeteran()), (vetForm) => {
      const application = JSON.parse(veteranToApplication(vetForm));
      const result = validate(application);

      if (!result) {
        /* eslint-disable no-console */
        console.log(JSON.stringify(application, null, 2));
        console.log(validate.errors);
        /* eslint-enable no-console */
      }
      expect(result).to.be.true;
    });
  });
});
<|endoftext|>


<commit_before>"use strict";

/**
 * Uses a SOAP endpoint in the cloud API to continuously poll for
 * available updates.
 *
 * The SOAP endpoint (`/ethComet.asmx`) is undocumented, but is the
 * one used by the [client web application](http://wirelesstag.net/media/mytaglist.com/apidoc.html).
 * The difference is that this implementation uses a proper SOAP API
 * interface (in contrast to hand-building the XML to be transmitted,
 * and to parsing the returned XML with a regex), and that it calls a
 * different method at that endpoint.
 *
 * This updater should receive updates resulting from armed sensors
 * going below or above their configured thresholds, or detecting
 * motion. The one caveat is that there is a short wait time (see
 * [UPDATE_LOOP_WAIT]{@link module:plugins/polling-updater~UPDATE_LOOP_WAIT}),
 * and an exponentially increasing wait time after errors (see
 * [WAIT_AFTER_ERROR]{@link module:plugins/polling-updater~WAIT_AFTER_ERROR})
 * until the next poll is issued. Updates falling into this time
 * period will only be caught at the next regular update interval
 * configured for a tag.
 *
 * @module
 */

var request = require('request'),
    soap = require('soap');

/**
 * @const {string} - the path (relative to `API_BASE_URI`) of the WSDL
 *                   endpoint description for polling
 * @default
 */
const WSDL_URL_PATH = "/ethComet.asmx?WSDL";
/**
 * @const {string} - the base URI of the polling API endpoint
 * @default
 */
const API_BASE_URI = "https://www.mytaglist.com";
/**
 * @const {number} - the time to wait between subsequent calls of the
 *                   polling endpoint (in milliseconds)
 * @default
 */
const UPDATE_LOOP_WAIT = 20;
/**
 * @const {number} - the minimum time to wait between subsequent calls of the
 *                   polling endpoint after an error occurred (in milliseconds)
 * @default
 */
const WAIT_AFTER_ERROR = 1000;

/**
 * Creates the updater instance.
 *
 * @param {WirelessTagPlatform} [platform] - Platform object through
 *                              which tags to be updated were (or are
 *                              going to be) found. Used solely for
 *                              possibly overriding configuration
 *                              options, specifically the base URI for
 *                              the cloud API endpoints.
 * @param {object} [config] - overriding configuration options
 * @param {string} [config.wsdl_url] - the full path for obtaining the
 *                              WSDL for the SOAP service to be polled
 *                              (default: [WSDL_URL_PATH]{@link
 *                              module:plugins/polling-updater~WSDL_URL_PATH})
 * @param {string} [config.apiBaseURI] - the base URI to use for API
 *                              endpoints (default:
 *                              [API_BASE_URI]{@link
 *                              module:plugins/polling-updater~API_BASE_URI})
 *
 * @constructor
 */
function PollingTagUpdater(platform, config) {
    this.tagsByUUID = {};
    this.options = Object.assign({}, config);
    if (! this.options.wsdl_url) {
        let apiBaseURI;
        if (platform) apiBaseURI = platform.apiBaseURI;
        if (config && config.apiBaseURI) apiBaseURI = config.apiBaseURI;
        if (! apiBaseURI) apiBaseURI = API_BASE_URI;
        this.options.wsdl_url = apiBaseURI + WSDL_URL_PATH;
    }
}

/**
 * Adds the given tags to the ones to be updated by this updater.
 *
 * @param {(WirelessTag|WirelessTag[])} tags - the tags (or the tag) to
 *                                           be updated
 *
 * @return {module:plugins/polling-updater~PollingTagUpdater}
 */
PollingTagUpdater.prototype.addTags = function(tags) {
    if (!Array.isArray(tags)) tags = [tags];
    for (let tag of tags) {
        this.tagsByUUID[tag.uuid] = tag;
    }
    return this;
};

/**
 * Removes the given tags from the ones to be updated by this updater.
 *
 * @param {(WirelessTag|WirelessTag[])} tags - the tags (or the tag) to
 *                                           be removed from updating
 *
 * @return {module:plugins/polling-updater~PollingTagUpdater}
 */
PollingTagUpdater.prototype.removeTags = function(tags) {
    if (tags && !Array.isArray(tags)) tags = [tags];
    for (let tag of tags) {
        delete this.tagsByUUID[tag.uuid];
    }
    return this;
};

/**
 * Starts the continuous update loop. Registered tags will get updated
 * until they are removed, or [stopUpdateLoop()]{@link module:plugins/polling-updater~PollingTagUpdater#stopUpdateLoop}
 * is called.
 *
 * @param {number} [waitTime] - the time to wait until scheduling the
 *                 next update; defaults to [UPDATE_LOOP_WAIT]{@link
 *                 module:plugins/polling-updater~UPDATE_LOOP_WAIT}.
 */
PollingTagUpdater.prototype.startUpdateLoop = function(waitTime) {
    if (waitTime === undefined) waitTime = UPDATE_LOOP_WAIT;
    if (this._updateTimer) return this._updateTimer;
    this._updateTimer = true; // placeholder to avoid race conditions
    let action = () => {
        this._updateTimer = true; // timer is done but action not yet
        this.apiClient().then((client) => {
            return pollForNextUpdate(client);
        }).then((tagDataList) => {
            tagDataList.forEach((tagData) => {
                updateTag(this.tagsByUUID[tagData.uuid], tagData);
            });
            // reset wait time upon success
            waitTime = undefined;
        }).catch((err) => {
            console.error(err.stack ? err.stack : err);
            waitTime = waitTime < WAIT_AFTER_ERROR ?
                WAIT_AFTER_ERROR : waitTime * 2;
        }).then(() => {
            // with the preceding catch() this is in essence a finally()
            if (this._updateTimer) {
                this._updateTimer = null;
                this.startUpdateLoop(waitTime);
            }
            // otherwise we have been cancelled while running the update
        });
    };
    // ensure that updates weren't cancelled since we entered here
    if (this._updateTimer === true) {
        this._updateTimer = setTimeout(action, waitTime);
    }
    return this._updateTimer;
};

/**
 * Stops the continuous update loop. Has no effect if an update loop
 * is not currently active.
 */
PollingTagUpdater.prototype.stopUpdateLoop = function() {
    let timer = this._updateTimer;
    this._updateTimer = null;   // avoid race conditions
    if (timer && timer !== true) {
        clearTimeout(timer);
    }
};

/**
 * If necessary creates, and otherwise obtains from cache the SOAP
 * client instance for the WSDL endpoint.
 *
 * @returns {Promise} Resolves to the SOAP client object.
 */
PollingTagUpdater.prototype.apiClient = function() {
    if (this.client) return Promise.resolve(this.client);
    return createSoapClient(this.options).then((client) => {
        this.client = client;
        return client;
    });
};

/**
 * Updates the tag corresponding to the given tag data. Does nothing
 * if the respective tag is undefined or null.
 *
 * @param {WirelessTag} tag - the tag object to be updated
 * @param {object} tagData - the data to update the tag object with;
 *                 this is normally returned from the API endpoint
 */
function updateTag(tag, tagData) {
    // if not a valid object for receiving updates, we are done
    if (! tag) return;
    // check that this is the current tag manager
    if (tagData.mac && (tag.wirelessTagManager.mac !== tagData.mac)) {
        throw new Error("expected tag " + tag.uuid
                        + " to be with tag manager " + tag.mac
                        + " but is reported to be with " + tagData.mac);
    }
    // we don't currently have anything more to do for the extra properties
    // identifying the tag manager, so simply get rid of them
    ['managerName','mac','dbid','mirrors'].forEach((k) => {
        delete tagData[k];
    });
    // almost done
    tag.data = tagData;
}

/**
 * Creates the SOAP client, using the supplied options for locating
 * the WSDL document for the endpoint.
 *
 * @param {object} [opts] - WSDL and SOAP endpoint options
 * @param {string} [opts.wsdl_url] - the URL from which to fetch the
 *                 WSDL document; defaults to the concatenation of
 *                 [API_BASE_URI]{@link
 *                 module:plugins/polling-updater~API_BASE_URI} and
 *                 [WSDL_URL_PATH]{@link
 *                 module:plugins/polling-updater~WSDL_URL_PATH}
 *
 * @returns {Promise} On success, resolves to the created SOAP client object
 */
function createSoapClient(opts) {
    let wsdl = opts && opts.wsdl_url ?
        opts.wsdl_url : API_BASE_URI + WSDL_URL_PATH;
    let clientOpts = { request: request.defaults({ jar: true, gzip: true }) };
    return new Promise((resolve, reject) => {
        soap.createClient(wsdl, clientOpts, (err, client) => {
            if (err) return reject(err);
            resolve(client);
        });
    });
}

/**
 * Polls the API endpoint for available updates and returns them.
 *
 * @param {object} client - the SOAP client object
 * @param {WirelessTagManager} [tagManager] - the tag manager to which
 *                             to restrict updates (this is currently ignored)
 *
 * @returns {Promise} On success, resolves to an array of tag data objects
 */
function pollForNextUpdate(client, tagManager) {
    return new Promise((resolve, reject) => {
        let methodName = tagManager ?
            "GetNextUpdateForAllManagersOnDB" :
            "GetNextUpdateForAllManagers";
        let soapMethod = client[methodName];
        let args = {};
        if (tagManager) args.dbid = tagManager.dbid;
        soapMethod(args, function(err, result) {
            if (err) return reject(err);
            let tagDataList = JSON.parse(result[methodName + "Result"]);
            resolve(tagDataList);
        });
    });
}

module.exports = PollingTagUpdater;
<commit_msg>Optimize endpoint polling if only one tag manager<commit_after>"use strict";

/**
 * Uses a SOAP endpoint in the cloud API to continuously poll for
 * available updates.
 *
 * The SOAP endpoint (`/ethComet.asmx`) is undocumented, but is the
 * one used by the [client web application](http://wirelesstag.net/media/mytaglist.com/apidoc.html).
 * The difference is that this implementation uses a proper SOAP API
 * interface (in contrast to hand-building the XML to be transmitted,
 * and to parsing the returned XML with a regex), and that it calls a
 * different method at that endpoint.
 *
 * This updater should receive updates resulting from armed sensors
 * going below or above their configured thresholds, or detecting
 * motion. The one caveat is that there is a short wait time (see
 * [UPDATE_LOOP_WAIT]{@link module:plugins/polling-updater~UPDATE_LOOP_WAIT}),
 * and an exponentially increasing wait time after errors (see
 * [WAIT_AFTER_ERROR]{@link module:plugins/polling-updater~WAIT_AFTER_ERROR})
 * until the next poll is issued. Updates falling into this time
 * period will only be caught at the next regular update interval
 * configured for a tag.
 *
 * @module
 */

var request = require('request'),
    soap = require('soap');

/**
 * @const {string} - the path (relative to `API_BASE_URI`) of the WSDL
 *                   endpoint description for polling
 * @default
 */
const WSDL_URL_PATH = "/ethComet.asmx?WSDL";
/**
 * @const {string} - the base URI of the polling API endpoint
 * @default
 */
const API_BASE_URI = "https://www.mytaglist.com";
/**
 * @const {number} - the time to wait between subsequent calls of the
 *                   polling endpoint (in milliseconds)
 * @default
 */
const UPDATE_LOOP_WAIT = 20;
/**
 * @const {number} - the minimum time to wait between subsequent calls of the
 *                   polling endpoint after an error occurred (in milliseconds)
 * @default
 */
const WAIT_AFTER_ERROR = 1000;

/**
 * Creates the updater instance.
 *
 * @param {WirelessTagPlatform} [platform] - Platform object through
 *                              which tags to be updated were (or are
 *                              going to be) found. Used solely for
 *                              possibly overriding configuration
 *                              options, specifically the base URI for
 *                              the cloud API endpoints.
 * @param {object} [config] - overriding configuration options
 * @param {string} [config.wsdl_url] - the full path for obtaining the
 *                              WSDL for the SOAP service to be polled
 *                              (default: [WSDL_URL_PATH]{@link
 *                              module:plugins/polling-updater~WSDL_URL_PATH})
 * @param {string} [config.apiBaseURI] - the base URI to use for API
 *                              endpoints (default:
 *                              [API_BASE_URI]{@link
 *                              module:plugins/polling-updater~API_BASE_URI})
 *
 * @constructor
 */
function PollingTagUpdater(platform, config) {
    this.tagsByUUID = {};
    this.options = Object.assign({}, config);
    if (! this.options.wsdl_url) {
        let apiBaseURI;
        if (platform) apiBaseURI = platform.apiBaseURI;
        if (config && config.apiBaseURI) apiBaseURI = config.apiBaseURI;
        if (! apiBaseURI) apiBaseURI = API_BASE_URI;
        this.options.wsdl_url = apiBaseURI + WSDL_URL_PATH;
    }
}

/**
 * Adds the given tags to the ones to be updated by this updater.
 *
 * @param {(WirelessTag|WirelessTag[])} tags - the tags (or the tag) to
 *                                           be updated
 *
 * @return {module:plugins/polling-updater~PollingTagUpdater}
 */
PollingTagUpdater.prototype.addTags = function(tags) {
    if (!Array.isArray(tags)) tags = [tags];
    for (let tag of tags) {
        this.tagsByUUID[tag.uuid] = tag;
    }
    return this;
};

/**
 * Removes the given tags from the ones to be updated by this updater.
 *
 * @param {(WirelessTag|WirelessTag[])} tags - the tags (or the tag) to
 *                                           be removed from updating
 *
 * @return {module:plugins/polling-updater~PollingTagUpdater}
 */
PollingTagUpdater.prototype.removeTags = function(tags) {
    if (tags && !Array.isArray(tags)) tags = [tags];
    for (let tag of tags) {
        delete this.tagsByUUID[tag.uuid];
    }
    return this;
};

/**
 * Starts the continuous update loop. Registered tags will get updated
 * until they are removed, or [stopUpdateLoop()]{@link module:plugins/polling-updater~PollingTagUpdater#stopUpdateLoop}
 * is called.
 *
 * @param {number} [waitTime] - the time to wait until scheduling the
 *                 next update; defaults to [UPDATE_LOOP_WAIT]{@link
 *                 module:plugins/polling-updater~UPDATE_LOOP_WAIT}.
 */
PollingTagUpdater.prototype.startUpdateLoop = function(waitTime) {
    if (waitTime === undefined) waitTime = UPDATE_LOOP_WAIT;
    if (this._updateTimer) return this._updateTimer;
    this._updateTimer = true; // placeholder to avoid race conditions
    let action = () => {
        this._updateTimer = true; // timer is done but action not yet
        this.apiClient().then((client) => {
            // if all tags are associated with a single tag manager,
            // limit updates to that tag manager
            let mgrs = Object.keys(this.tagsByUUID).map((uuid) => {
                return this.tagsByUUID[uuid].wirelessTagManager;
            }).reduce((all, mgr) => {
                if (all.length === 0 || all[0].mac !== mgr.mac) all.push(mgr);
                return all;
            }, []);
            return pollForNextUpdate(client,
                                     mgrs.length === 1 ? mgrs[0] : undefined);
        }).then((tagDataList) => {
            tagDataList.forEach((tagData) => {
                updateTag(this.tagsByUUID[tagData.uuid], tagData);
            });
            // reset wait time upon success
            waitTime = undefined;
        }).catch((err) => {
            console.error(err.stack ? err.stack : err);
            waitTime = waitTime < WAIT_AFTER_ERROR ?
                WAIT_AFTER_ERROR : waitTime * 2;
        }).then(() => {
            // with the preceding catch() this is in essence a finally()
            if (this._updateTimer) {
                this._updateTimer = null;
                this.startUpdateLoop(waitTime);
            }
            // otherwise we have been cancelled while running the update
        });
    };
    // ensure that updates weren't cancelled since we entered here
    if (this._updateTimer === true) {
        this._updateTimer = setTimeout(action, waitTime);
    }
    return this._updateTimer;
};

/**
 * Stops the continuous update loop. Has no effect if an update loop
 * is not currently active.
 */
PollingTagUpdater.prototype.stopUpdateLoop = function() {
    let timer = this._updateTimer;
    this._updateTimer = null;   // avoid race conditions
    if (timer && timer !== true) {
        clearTimeout(timer);
    }
};

/**
 * If necessary creates, and otherwise obtains from cache the SOAP
 * client instance for the WSDL endpoint.
 *
 * @returns {Promise} Resolves to the SOAP client object.
 */
PollingTagUpdater.prototype.apiClient = function() {
    if (this.client) return Promise.resolve(this.client);
    return createSoapClient(this.options).then((client) => {
        this.client = client;
        return client;
    });
};

/**
 * Updates the tag corresponding to the given tag data. Does nothing
 * if the respective tag is undefined or null.
 *
 * @param {WirelessTag} tag - the tag object to be updated
 * @param {object} tagData - the data to update the tag object with;
 *                 this is normally returned from the API endpoint
 */
function updateTag(tag, tagData) {
    // if not a valid object for receiving updates, we are done
    if (! tag) return;
    // check that this is the current tag manager
    if (tagData.mac && (tag.wirelessTagManager.mac !== tagData.mac)) {
        throw new Error("expected tag " + tag.uuid
                        + " to be with tag manager " + tag.mac
                        + " but is reported to be with " + tagData.mac);
    }
    // we don't currently have anything more to do for the extra properties
    // identifying the tag manager, so simply get rid of them
    ['managerName','mac','dbid','mirrors'].forEach((k) => {
        delete tagData[k];
    });
    // almost done
    tag.data = tagData;
}

/**
 * Creates the SOAP client, using the supplied options for locating
 * the WSDL document for the endpoint.
 *
 * @param {object} [opts] - WSDL and SOAP endpoint options
 * @param {string} [opts.wsdl_url] - the URL from which to fetch the
 *                 WSDL document; defaults to the concatenation of
 *                 [API_BASE_URI]{@link
 *                 module:plugins/polling-updater~API_BASE_URI} and
 *                 [WSDL_URL_PATH]{@link
 *                 module:plugins/polling-updater~WSDL_URL_PATH}
 *
 * @returns {Promise} On success, resolves to the created SOAP client object
 */
function createSoapClient(opts) {
    let wsdl = opts && opts.wsdl_url ?
        opts.wsdl_url : API_BASE_URI + WSDL_URL_PATH;
    let clientOpts = { request: request.defaults({ jar: true, gzip: true }) };
    return new Promise((resolve, reject) => {
        soap.createClient(wsdl, clientOpts, (err, client) => {
            if (err) return reject(err);
            resolve(client);
        });
    });
}

/**
 * Polls the API endpoint for available updates and returns them.
 *
 * @param {object} client - the SOAP client object
 * @param {WirelessTagManager} [tagManager] - the tag manager to which
 *                             to restrict updates (this is currently ignored)
 *
 * @returns {Promise} On success, resolves to an array of tag data objects
 */
function pollForNextUpdate(client, tagManager) {
    return new Promise((resolve, reject) => {
        let methodName = tagManager ?
            "GetNextUpdateForAllManagersOnDB" :
            "GetNextUpdateForAllManagers";
        let soapMethod = client[methodName];
        let args = {};
        if (tagManager) args.dbid = tagManager.dbid;
        soapMethod(args, function(err, result) {
            if (err) return reject(err);
            let tagDataList = JSON.parse(result[methodName + "Result"]);
            resolve(tagDataList);
        });
    });
}

module.exports = PollingTagUpdater;
<|endoftext|>