<commit_before>'use strict';
const EventEmitter = require('events');
const MongoError = require('../error').MongoError;
const Pool = require('../connection/pool');
const relayEvents = require('../utils').relayEvents;
const wireProtocol = require('../wireprotocol');
const BSON = require('../connection/utils').retrieveBSON();
const createClientInfo = require('../topologies/shared').createClientInfo;
const Logger = require('../connection/logger');
const ServerDescription = require('./server_description').ServerDescription;
const ReadPreference = require('../topologies/read_preference');
const monitorServer = require('./monitoring').monitorServer;
const MongoParseError = require('../error').MongoParseError;
const MongoNetworkError = require('../error').MongoNetworkError;
const collationNotSupported = require('../utils').collationNotSupported;
const debugOptions = require('../connection/utils').debugOptions;
const isSDAMUnrecoverableError = require('../error').isSDAMUnrecoverableError;

// Used for filtering out fields for logging
const DEBUG_FIELDS = [
  'reconnect',
  'reconnectTries',
  'reconnectInterval',
  'emitError',
  'cursorFactory',
  'host',
  'port',
  'size',
  'keepAlive',
  'keepAliveInitialDelay',
  'noDelay',
  'connectionTimeout',
  'checkServerIdentity',
  'socketTimeout',
  'ssl',
  'ca',
  'crl',
  'cert',
  'key',
  'rejectUnauthorized',
  'promoteLongs',
  'promoteValues',
  'promoteBuffers',
  'servername'
];

const STATE_DISCONNECTED = 0;
const STATE_CONNECTING = 1;
const STATE_CONNECTED = 2;

/**
 *
 * @fires Server#serverHeartbeatStarted
 * @fires Server#serverHeartbeatSucceeded
 * @fires Server#serverHeartbeatFailed
 */
class Server extends EventEmitter {
  /**
   * Create a server
   *
   * @param {ServerDescription} description
   * @param {Object} options
   */
  constructor(description, options, topology) {
    super();

    this.s = {
      // the server description
      description,
      // a saved copy of the incoming options
      options,
      // the server logger
      logger: Logger('Server', options),
      // the bson parser
      bson: options.bson || new BSON(),
      // client metadata for the initial handshake
      clientInfo: createClientInfo(options),
      // state variable to determine if there is an active server check in progress
      monitoring: false,
      // the implementation of the monitoring method
      monitorFunction: options.monitorFunction || monitorServer,
      // the connection pool
      pool: null,
      // the server state
      state: STATE_DISCONNECTED,
      credentials: options.credentials,
      topology
    };
  }

  get description() {
    return this.s.description;
  }

  get name() {
    return this.s.description.address;
  }

  get autoEncrypter() {
    if (this.s.options && this.s.options.autoEncrypter) {
      return this.s.options.autoEncrypter;
    }
    return null;
  }

  /**
   * Initiate server connect
   */
  connect(options) {
    options = options || {};

    // do not allow connect to be called on anything that's not disconnected
    if (this.s.pool && !this.s.pool.isDisconnected() && !this.s.pool.isDestroyed()) {
      throw new MongoError(`Server instance in invalid state ${this.s.pool.state}`);
    }

    // create a pool
    const addressParts = this.description.address.split(':');
    const poolOptions = Object.assign(
      { host: addressParts[0], port: parseInt(addressParts[1], 10) },
      this.s.options,
      options,
      { bson: this.s.bson }
    );

    // NOTE: this should only be the case if we are connecting to a single server
    poolOptions.reconnect = true;

    this.s.pool = new Pool(this, poolOptions);

    // setup listeners
    this.s.pool.on('connect', connectEventHandler(this));
    this.s.pool.on('close', errorEventHandler(this));
    this.s.pool.on('error', errorEventHandler(this));
    this.s.pool.on('parseError', parseErrorEventHandler(this));

    // it is unclear whether consumers should even know about these events
    // this.s.pool.on('timeout', timeoutEventHandler(this));
    // this.s.pool.on('reconnect', reconnectEventHandler(this));
    // this.s.pool.on('reconnectFailed', errorEventHandler(this));

    // relay all command monitoring events
    relayEvents(this.s.pool, this, ['commandStarted', 'commandSucceeded', 'commandFailed']);

    this.s.state = STATE_CONNECTING;

    // If auth settings have been provided, use them
    if (options.auth) {
      this.s.pool.connect.apply(this.s.pool, options.auth);
      return;
    }

    this.s.pool.connect();
  }

  /**
   * Destroy the server connection
   *
   * @param {Boolean} [options.force=false] Force destroy the pool
   */
  destroy(options, callback) {
    if (typeof options === 'function') (callback = options), (options = {});
    options = Object.assign({}, { force: false }, options);

    const done = err => {
      this.emit('closed');
      this.s.state = STATE_DISCONNECTED;
      if (typeof callback === 'function') {
        callback(err, null);
      }
    };

    if (!this.s.pool) {
      return done();
    }

    ['close', 'error', 'timeout', 'parseError', 'connect'].forEach(event => {
      this.s.pool.removeAllListeners(event);
    });

    if (this.s.monitorId) {
      clearTimeout(this.s.monitorId);
    }

    this.s.pool.destroy(options.force, done);
  }

  /**
   * Immediately schedule monitoring of this server. If there already an attempt being made
   * this will be a no-op.
   */
  monitor(options) {
    options = options || {};
    if (this.s.state !== STATE_CONNECTED || this.s.monitoring) return;
    if (this.s.monitorId) clearTimeout(this.s.monitorId);
    this.s.monitorFunction(this, options);
  }

  /**
   * Execute a command
   *
   * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
   * @param {object} cmd The command hash
   * @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
   * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
   * @param {Boolean} [options.checkKeys=false] Specify if the bson parser should validate keys.
   * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
   * @param {Boolean} [options.fullResult=false] Return the full envelope instead of just the result document.
   * @param {ClientSession} [options.session=null] Session to use for the operation
   * @param {opResultCallback} callback A callback function
   */
  command(ns, cmd, options, callback) {
    if (typeof options === 'function') {
      (callback = options), (options = {}), (options = options || {});
    }

    const error = basicReadValidations(this, options);
    if (error) {
      return callback(error, null);
    }

    // Clone the options
    options = Object.assign({}, options, { wireProtocolCommand: false });

    // Debug log
    if (this.s.logger.isDebug()) {
      this.s.logger.debug(
        `executing command [${JSON.stringify({
          ns,
          cmd,
          options: debugOptions(DEBUG_FIELDS, options)
        })}] against ${this.name}`
      );
    }

    // error if collation not supported
    if (collationNotSupported(this, cmd)) {
      callback(new MongoError(`server ${this.name} does not support collation`));
      return;
    }

    wireProtocol.command(this, ns, cmd, options, (err, result) => {
      if (err && err instanceof MongoNetworkError) {
        if (options.session) {
          options.session.serverSession.isDirty = true;
        }
      }

      if (err && isSDAMUnrecoverableError(err)) {
        this.emit('error', err);
      }

      callback(err, result);
    });
  }

  /**
   * Execute a query against the server
   *
   * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
   * @param {object} cmd The command document for the query
   * @param {object} options Optional settings
   * @param {function} callback
   */
  query(ns, cmd, cursorState, options, callback) {
    wireProtocol.query(this, ns, cmd, cursorState, options, (err, result) => {
      if (err && err instanceof MongoNetworkError) {
        if (options.session) {
          options.session.serverSession.isDirty = true;
        }
      }

      if (err && isSDAMUnrecoverableError(err)) {
        this.emit('error', err);
      }

      callback(err, result);
    });
  }

  /**
   * Execute a `getMore` against the server
   *
   * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
   * @param {object} cursorState State data associated with the cursor calling this method
   * @param {object} options Optional settings
   * @param {function} callback
   */
  getMore(ns, cursorState, batchSize, options, callback) {
    wireProtocol.getMore(this, ns, cursorState, batchSize, options, (err, result) => {
      if (err && err instanceof MongoNetworkError) {
        if (options.session) {
          options.session.serverSession.isDirty = true;
        }
      }

      if (err && isSDAMUnrecoverableError(err)) {
        this.emit('error', err);
      }

      callback(err, result);
    });
  }

  /**
   * Execute a `killCursors` command against the server
   *
   * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
   * @param {object} cursorState State data associated with the cursor calling this method
   * @param {function} callback
   */
  killCursors(ns, cursorState, callback) {
    wireProtocol.killCursors(this, ns, cursorState, (err, result) => {
      if (err && isSDAMUnrecoverableError(err)) {
        this.emit('error', err);
      }

      if (typeof callback === 'function') {
        callback(err, result);
      }
    });
  }

  /**
   * Insert one or more documents
   * @method
   * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
   * @param {array} ops An array of documents to insert
   * @param {boolean} [options.ordered=true] Execute in order or out of order
   * @param {object} [options.writeConcern={}] Write concern for the operation
   * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
   * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
   * @param {ClientSession} [options.session=null] Session to use for the operation
   * @param {opResultCallback} callback A callback function
   */
  insert(ns, ops, options, callback) {
    executeWriteOperation({ server: this, op: 'insert', ns, ops }, options, callback);
  }

  /**
   * Perform one or more update operations
   * @method
   * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
   * @param {array} ops An array of updates
   * @param {boolean} [options.ordered=true] Execute in order or out of order
   * @param {object} [options.writeConcern={}] Write concern for the operation
   * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
   * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
   * @param {ClientSession} [options.session=null] Session to use for the operation
   * @param {opResultCallback} callback A callback function
   */
  update(ns, ops, options, callback) {
    executeWriteOperation({ server: this, op: 'update', ns, ops }, options, callback);
  }

  /**
   * Perform one or more remove operations
   * @method
   * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
   * @param {array} ops An array of removes
   * @param {boolean} [options.ordered=true] Execute in order or out of order
   * @param {object} [options.writeConcern={}] Write concern for the operation
   * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
   * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
   * @param {ClientSession} [options.session=null] Session to use for the operation
   * @param {opResultCallback} callback A callback function
   */
  remove(ns, ops, options, callback) {
    executeWriteOperation({ server: this, op: 'remove', ns, ops }, options, callback);
  }
}

Object.defineProperty(Server.prototype, 'clusterTime', {
  get: function() {
    return this.s.topology.clusterTime;
  },
  set: function(clusterTime) {
    this.s.topology.clusterTime = clusterTime;
  }
});

function basicWriteValidations(server) {
  if (!server.s.pool) {
    return new MongoError('server instance is not connected');
  }

  if (server.s.pool.isDestroyed()) {
    return new MongoError('server instance pool was destroyed');
  }

  return null;
}

function basicReadValidations(server, options) {
  const error = basicWriteValidations(server, options);
  if (error) {
    return error;
  }

  if (options.readPreference && !(options.readPreference instanceof ReadPreference)) {
    return new MongoError('readPreference must be an instance of ReadPreference');
  }
}

function executeWriteOperation(args, options, callback) {
  if (typeof options === 'function') (callback = options), (options = {});
  options = options || {};

  // TODO: once we drop Node 4, use destructuring either here or in arguments.
  const server = args.server;
  const op = args.op;
  const ns = args.ns;
  const ops = Array.isArray(args.ops) ? args.ops : [args.ops];

  const error = basicWriteValidations(server, options);
  if (error) {
    callback(error, null);
    return;
  }

  if (collationNotSupported(server, options)) {
    callback(new MongoError(`server ${this.name} does not support collation`));
    return;
  }

  return wireProtocol[op](server, ns, ops, options, (err, result) => {
    if (err && err instanceof MongoNetworkError) {
      if (options.session) {
        options.session.serverSession.isDirty = true;
      }
    }

    if (err && isSDAMUnrecoverableError(err)) {
      server.emit('error', err);
    }

    callback(err, result);
  });
}

function connectEventHandler(server) {
  return function(pool, conn) {
    const ismaster = conn.ismaster;
    server.s.lastIsMasterMS = conn.lastIsMasterMS;
    if (conn.agreedCompressor) {
      server.s.pool.options.agreedCompressor = conn.agreedCompressor;
    }

    if (conn.zlibCompressionLevel) {
      server.s.pool.options.zlibCompressionLevel = conn.zlibCompressionLevel;
    }

    if (conn.ismaster.$clusterTime) {
      const $clusterTime = conn.ismaster.$clusterTime;
      server.s.sclusterTime = $clusterTime;
    }

    // log the connection event if requested
    if (server.s.logger.isInfo()) {
      server.s.logger.info(
        `server ${server.name} connected with ismaster [${JSON.stringify(ismaster)}]`
      );
    }

    // emit an event indicating that our description has changed
    server.emit('descriptionReceived', new ServerDescription(server.description.address, ismaster));

    // we are connected and handshaked (guaranteed by the pool)
    server.s.state = STATE_CONNECTED;
    server.emit('connect', server);
  };
}

function errorEventHandler(server) {
  return function(err) {
    if (err) {
      server.emit('error', new MongoNetworkError(err));
    }

    server.emit('close');
  };
}

function parseErrorEventHandler(server) {
  return function(err) {
    server.s.state = STATE_DISCONNECTED;
    server.emit('error', new MongoParseError(err));
  };
}

module.exports = Server;
<commit_msg>refactor: simplify error handling checks<commit_after>'use strict';
const EventEmitter = require('events');
const MongoError = require('../error').MongoError;
const Pool = require('../connection/pool');
const relayEvents = require('../utils').relayEvents;
const wireProtocol = require('../wireprotocol');
const BSON = require('../connection/utils').retrieveBSON();
const createClientInfo = require('../topologies/shared').createClientInfo;
const Logger = require('../connection/logger');
const ServerDescription = require('./server_description').ServerDescription;
const ReadPreference = require('../topologies/read_preference');
const monitorServer = require('./monitoring').monitorServer;
const MongoParseError = require('../error').MongoParseError;
const MongoNetworkError = require('../error').MongoNetworkError;
const collationNotSupported = require('../utils').collationNotSupported;
const debugOptions = require('../connection/utils').debugOptions;
const isSDAMUnrecoverableError = require('../error').isSDAMUnrecoverableError;

// Used for filtering out fields for logging
const DEBUG_FIELDS = [
  'reconnect',
  'reconnectTries',
  'reconnectInterval',
  'emitError',
  'cursorFactory',
  'host',
  'port',
  'size',
  'keepAlive',
  'keepAliveInitialDelay',
  'noDelay',
  'connectionTimeout',
  'checkServerIdentity',
  'socketTimeout',
  'ssl',
  'ca',
  'crl',
  'cert',
  'key',
  'rejectUnauthorized',
  'promoteLongs',
  'promoteValues',
  'promoteBuffers',
  'servername'
];

const STATE_DISCONNECTED = 0;
const STATE_CONNECTING = 1;
const STATE_CONNECTED = 2;

/**
 *
 * @fires Server#serverHeartbeatStarted
 * @fires Server#serverHeartbeatSucceeded
 * @fires Server#serverHeartbeatFailed
 */
class Server extends EventEmitter {
  /**
   * Create a server
   *
   * @param {ServerDescription} description
   * @param {Object} options
   */
  constructor(description, options, topology) {
    super();

    this.s = {
      // the server description
      description,
      // a saved copy of the incoming options
      options,
      // the server logger
      logger: Logger('Server', options),
      // the bson parser
      bson: options.bson || new BSON(),
      // client metadata for the initial handshake
      clientInfo: createClientInfo(options),
      // state variable to determine if there is an active server check in progress
      monitoring: false,
      // the implementation of the monitoring method
      monitorFunction: options.monitorFunction || monitorServer,
      // the connection pool
      pool: null,
      // the server state
      state: STATE_DISCONNECTED,
      credentials: options.credentials,
      topology
    };
  }

  get description() {
    return this.s.description;
  }

  get name() {
    return this.s.description.address;
  }

  get autoEncrypter() {
    if (this.s.options && this.s.options.autoEncrypter) {
      return this.s.options.autoEncrypter;
    }
    return null;
  }

  /**
   * Initiate server connect
   */
  connect(options) {
    options = options || {};

    // do not allow connect to be called on anything that's not disconnected
    if (this.s.pool && !this.s.pool.isDisconnected() && !this.s.pool.isDestroyed()) {
      throw new MongoError(`Server instance in invalid state ${this.s.pool.state}`);
    }

    // create a pool
    const addressParts = this.description.address.split(':');
    const poolOptions = Object.assign(
      { host: addressParts[0], port: parseInt(addressParts[1], 10) },
      this.s.options,
      options,
      { bson: this.s.bson }
    );

    // NOTE: this should only be the case if we are connecting to a single server
    poolOptions.reconnect = true;

    this.s.pool = new Pool(this, poolOptions);

    // setup listeners
    this.s.pool.on('connect', connectEventHandler(this));
    this.s.pool.on('close', errorEventHandler(this));
    this.s.pool.on('error', errorEventHandler(this));
    this.s.pool.on('parseError', parseErrorEventHandler(this));

    // it is unclear whether consumers should even know about these events
    // this.s.pool.on('timeout', timeoutEventHandler(this));
    // this.s.pool.on('reconnect', reconnectEventHandler(this));
    // this.s.pool.on('reconnectFailed', errorEventHandler(this));

    // relay all command monitoring events
    relayEvents(this.s.pool, this, ['commandStarted', 'commandSucceeded', 'commandFailed']);

    this.s.state = STATE_CONNECTING;

    // If auth settings have been provided, use them
    if (options.auth) {
      this.s.pool.connect.apply(this.s.pool, options.auth);
      return;
    }

    this.s.pool.connect();
  }

  /**
   * Destroy the server connection
   *
   * @param {Boolean} [options.force=false] Force destroy the pool
   */
  destroy(options, callback) {
    if (typeof options === 'function') (callback = options), (options = {});
    options = Object.assign({}, { force: false }, options);

    const done = err => {
      this.emit('closed');
      this.s.state = STATE_DISCONNECTED;
      if (typeof callback === 'function') {
        callback(err, null);
      }
    };

    if (!this.s.pool) {
      return done();
    }

    ['close', 'error', 'timeout', 'parseError', 'connect'].forEach(event => {
      this.s.pool.removeAllListeners(event);
    });

    if (this.s.monitorId) {
      clearTimeout(this.s.monitorId);
    }

    this.s.pool.destroy(options.force, done);
  }

  /**
   * Immediately schedule monitoring of this server. If there already an attempt being made
   * this will be a no-op.
   */
  monitor(options) {
    options = options || {};
    if (this.s.state !== STATE_CONNECTED || this.s.monitoring) return;
    if (this.s.monitorId) clearTimeout(this.s.monitorId);
    this.s.monitorFunction(this, options);
  }

  /**
   * Execute a command
   *
   * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
   * @param {object} cmd The command hash
   * @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
   * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
   * @param {Boolean} [options.checkKeys=false] Specify if the bson parser should validate keys.
   * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
   * @param {Boolean} [options.fullResult=false] Return the full envelope instead of just the result document.
   * @param {ClientSession} [options.session=null] Session to use for the operation
   * @param {opResultCallback} callback A callback function
   */
  command(ns, cmd, options, callback) {
    if (typeof options === 'function') {
      (callback = options), (options = {}), (options = options || {});
    }

    const error = basicReadValidations(this, options);
    if (error) {
      return callback(error, null);
    }

    // Clone the options
    options = Object.assign({}, options, { wireProtocolCommand: false });

    // Debug log
    if (this.s.logger.isDebug()) {
      this.s.logger.debug(
        `executing command [${JSON.stringify({
          ns,
          cmd,
          options: debugOptions(DEBUG_FIELDS, options)
        })}] against ${this.name}`
      );
    }

    // error if collation not supported
    if (collationNotSupported(this, cmd)) {
      callback(new MongoError(`server ${this.name} does not support collation`));
      return;
    }

    wireProtocol.command(this, ns, cmd, options, (err, result) => {
      if (err) {
        if (options.session && err instanceof MongoNetworkError) {
          options.session.serverSession.isDirty = true;
        }

        if (isSDAMUnrecoverableError(err)) {
          this.emit('error', err);
        }
      }

      callback(err, result);
    });
  }

  /**
   * Execute a query against the server
   *
   * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
   * @param {object} cmd The command document for the query
   * @param {object} options Optional settings
   * @param {function} callback
   */
  query(ns, cmd, cursorState, options, callback) {
    wireProtocol.query(this, ns, cmd, cursorState, options, (err, result) => {
      if (err) {
        if (options.session && err instanceof MongoNetworkError) {
          options.session.serverSession.isDirty = true;
        }

        if (isSDAMUnrecoverableError(err)) {
          this.emit('error', err);
        }
      }

      callback(err, result);
    });
  }

  /**
   * Execute a `getMore` against the server
   *
   * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
   * @param {object} cursorState State data associated with the cursor calling this method
   * @param {object} options Optional settings
   * @param {function} callback
   */
  getMore(ns, cursorState, batchSize, options, callback) {
    wireProtocol.getMore(this, ns, cursorState, batchSize, options, (err, result) => {
      if (err) {
        if (options.session && err instanceof MongoNetworkError) {
          options.session.serverSession.isDirty = true;
        }

        if (isSDAMUnrecoverableError(err)) {
          this.emit('error', err);
        }
      }

      callback(err, result);
    });
  }

  /**
   * Execute a `killCursors` command against the server
   *
   * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
   * @param {object} cursorState State data associated with the cursor calling this method
   * @param {function} callback
   */
  killCursors(ns, cursorState, callback) {
    wireProtocol.killCursors(this, ns, cursorState, (err, result) => {
      if (err && isSDAMUnrecoverableError(err)) {
        this.emit('error', err);
      }

      if (typeof callback === 'function') {
        callback(err, result);
      }
    });
  }

  /**
   * Insert one or more documents
   * @method
   * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
   * @param {array} ops An array of documents to insert
   * @param {boolean} [options.ordered=true] Execute in order or out of order
   * @param {object} [options.writeConcern={}] Write concern for the operation
   * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
   * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
   * @param {ClientSession} [options.session=null] Session to use for the operation
   * @param {opResultCallback} callback A callback function
   */
  insert(ns, ops, options, callback) {
    executeWriteOperation({ server: this, op: 'insert', ns, ops }, options, callback);
  }

  /**
   * Perform one or more update operations
   * @method
   * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
   * @param {array} ops An array of updates
   * @param {boolean} [options.ordered=true] Execute in order or out of order
   * @param {object} [options.writeConcern={}] Write concern for the operation
   * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
   * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
   * @param {ClientSession} [options.session=null] Session to use for the operation
   * @param {opResultCallback} callback A callback function
   */
  update(ns, ops, options, callback) {
    executeWriteOperation({ server: this, op: 'update', ns, ops }, options, callback);
  }

  /**
   * Perform one or more remove operations
   * @method
   * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
   * @param {array} ops An array of removes
   * @param {boolean} [options.ordered=true] Execute in order or out of order
   * @param {object} [options.writeConcern={}] Write concern for the operation
   * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
   * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
   * @param {ClientSession} [options.session=null] Session to use for the operation
   * @param {opResultCallback} callback A callback function
   */
  remove(ns, ops, options, callback) {
    executeWriteOperation({ server: this, op: 'remove', ns, ops }, options, callback);
  }
}

Object.defineProperty(Server.prototype, 'clusterTime', {
  get: function() {
    return this.s.topology.clusterTime;
  },
  set: function(clusterTime) {
    this.s.topology.clusterTime = clusterTime;
  }
});

function basicWriteValidations(server) {
  if (!server.s.pool) {
    return new MongoError('server instance is not connected');
  }

  if (server.s.pool.isDestroyed()) {
    return new MongoError('server instance pool was destroyed');
  }

  return null;
}

function basicReadValidations(server, options) {
  const error = basicWriteValidations(server, options);
  if (error) {
    return error;
  }

  if (options.readPreference && !(options.readPreference instanceof ReadPreference)) {
    return new MongoError('readPreference must be an instance of ReadPreference');
  }
}

function executeWriteOperation(args, options, callback) {
  if (typeof options === 'function') (callback = options), (options = {});
  options = options || {};

  // TODO: once we drop Node 4, use destructuring either here or in arguments.
  const server = args.server;
  const op = args.op;
  const ns = args.ns;
  const ops = Array.isArray(args.ops) ? args.ops : [args.ops];

  const error = basicWriteValidations(server, options);
  if (error) {
    callback(error, null);
    return;
  }

  if (collationNotSupported(server, options)) {
    callback(new MongoError(`server ${this.name} does not support collation`));
    return;
  }

  return wireProtocol[op](server, ns, ops, options, (err, result) => {
    if (err) {
      if (options.session && err instanceof MongoNetworkError) {
        options.session.serverSession.isDirty = true;
      }

      if (isSDAMUnrecoverableError(err)) {
        server.emit('error', err);
      }
    }

    callback(err, result);
  });
}

function connectEventHandler(server) {
  return function(pool, conn) {
    const ismaster = conn.ismaster;
    server.s.lastIsMasterMS = conn.lastIsMasterMS;
    if (conn.agreedCompressor) {
      server.s.pool.options.agreedCompressor = conn.agreedCompressor;
    }

    if (conn.zlibCompressionLevel) {
      server.s.pool.options.zlibCompressionLevel = conn.zlibCompressionLevel;
    }

    if (conn.ismaster.$clusterTime) {
      const $clusterTime = conn.ismaster.$clusterTime;
      server.s.sclusterTime = $clusterTime;
    }

    // log the connection event if requested
    if (server.s.logger.isInfo()) {
      server.s.logger.info(
        `server ${server.name} connected with ismaster [${JSON.stringify(ismaster)}]`
      );
    }

    // emit an event indicating that our description has changed
    server.emit('descriptionReceived', new ServerDescription(server.description.address, ismaster));

    // we are connected and handshaked (guaranteed by the pool)
    server.s.state = STATE_CONNECTED;
    server.emit('connect', server);
  };
}

function errorEventHandler(server) {
  return function(err) {
    if (err) {
      server.emit('error', new MongoNetworkError(err));
    }

    server.emit('close');
  };
}

function parseErrorEventHandler(server) {
  return function(err) {
    server.s.state = STATE_DISCONNECTED;
    server.emit('error', new MongoParseError(err));
  };
}

module.exports = Server;
<|endoftext|>


<commit_before>import orderBy from 'lodash/orderBy'
import { createSelector } from 'reselect'

import { FAMILY_ANALYSIS_STATUS_OPTIONS } from 'shared/utils/constants'

import { getProjectsByGuid, getFamiliesByGuid, getIndividualsByGuid, getSamplesByGuid } from 'redux/selectors'

import {
  SHOW_ALL,
  SORT_BY_FAMILY_NAME,
  CASE_REVIEW_STATUS_OPTIONS,
  FAMILY_FILTER_OPTIONS,
  FAMILY_SORT_OPTIONS,
  SORT_BY_FAMILY_GUID,
  VARIANT_SORT_OPTONS,
  VARIANT_EXPORT_DATA,
  VARIANT_GENOTYPE_EXPORT_DATA,
} from './constants'

const FAMILY_FILTER_LOOKUP = FAMILY_FILTER_OPTIONS.reduce(
  (acc, opt) => ({
    ...acc,
    [opt.value]: opt.createFilter,
  }), {},
)

const FAMILY_SORT_LOOKUP = FAMILY_SORT_OPTIONS.reduce(
  (acc, opt) => ({
    ...acc,
    [opt.value]: opt.createSortKeyGetter,
  }), {},
)

const VARIANT_SORT_LOOKUP = VARIANT_SORT_OPTONS.reduce(
  (acc, opt) => ({
    ...acc,
    [opt.value]: opt.comparator,
  }), {},
)

// project data selectors

export const getProjectDetailsIsLoading = state => state.projectDetailsLoading.isLoading
export const getProjectSavedVariantsIsLoading = state => state.projectSavedVariantsLoading.isLoading
export const getProjectGuid = state => state.currentProjectGuid

export const getProject = createSelector(
  getProjectsByGuid, getProjectGuid, (projectsByGuid, currentProjectGuid) => projectsByGuid[currentProjectGuid],
)

const groupEntitiesByProjectGuid = entities => Object.entries(entities).reduce((acc, [entityGuid, entity]) => {
  if (!(entity.projectGuid in acc)) {
    acc[entity.projectGuid] = {}
  }
  acc[entity.projectGuid][entityGuid] = entity

  return acc

}, {})

const selectEntitiesForProjectGuid = (entitiesGroupedByProjectGuid, projectGuid) => entitiesGroupedByProjectGuid[projectGuid]

export const getFamiliesGroupedByProjectGuid = createSelector(getFamiliesByGuid, groupEntitiesByProjectGuid)
export const getIndividualsGroupedByProjectGuid = createSelector(getIndividualsByGuid, groupEntitiesByProjectGuid)
export const getSamplesGroupedByProjectGuid = createSelector(getSamplesByGuid, groupEntitiesByProjectGuid)

export const getProjectFamiliesByGuid = createSelector(getFamiliesGroupedByProjectGuid, getProjectGuid, selectEntitiesForProjectGuid)
export const getProjectIndividualsByGuid = createSelector(getIndividualsGroupedByProjectGuid, getProjectGuid, selectEntitiesForProjectGuid)
export const getProjectSamplesByGuid = createSelector(getSamplesGroupedByProjectGuid, getProjectGuid, selectEntitiesForProjectGuid)

// Saved variant selectors
export const getSavedVariantTableState = state => state.savedVariantTableState
export const getSavedVariantCategoryFilter = state => state.savedVariantTableState.categoryFilter || SHOW_ALL
export const getSavedVariantSortOrder = state => state.savedVariantTableState.sortOrder || SORT_BY_FAMILY_GUID
export const getSavedVariantHideExcluded = state => state.savedVariantTableState.hideExcluded
export const getSavedVariantCurrentPage = state => state.savedVariantTableState.currentPage || 1
export const getSavedVariantRecordsPerPage = state => state.savedVariantTableState.recordsPerPage || 25

export const getProjectSavedVariants = createSelector(
  state => state.projectSavedVariants,
  (state, props) => props.match.params,
  (projectSavedVariants, { tag, familyGuid, variantGuid }) => {
    let variants = Object.values(projectSavedVariants)
    if (variantGuid) {
      return variants.filter(o => o.variantId === variantGuid)
    }
    if (familyGuid) {
      variants = variants.filter(o => o.familyGuid === familyGuid)
    }
    return tag ? variants.filter(o => o.tags.some(t => t.name === tag)) : variants
  },
)

export const getSavedVariantVisibleIndices = createSelector(
  getSavedVariantCurrentPage, getSavedVariantRecordsPerPage,
  (currentPage, recordsPerPage) => {
    return [(currentPage - 1) * recordsPerPage, currentPage * recordsPerPage]
  },
)

export const getFilteredProjectSavedVariants = createSelector(
  getProjectSavedVariants,
  getSavedVariantCategoryFilter,
  getSavedVariantHideExcluded,
  (projectSavedVariants, categoryFilter, hideExcluded) => {
    let variantsToShow = projectSavedVariants
    if (hideExcluded) {
      variantsToShow = variantsToShow.filter(variant => variant.tags.every(t => t.name !== 'Excluded'))
    }
    if (categoryFilter !== SHOW_ALL) {
      variantsToShow = variantsToShow.filter(variant => variant.tags.some(t => t.category === categoryFilter))
    }
    return variantsToShow
  },
)

export const getVisibleSortedProjectSavedVariants = createSelector(
  getFilteredProjectSavedVariants,
  getSavedVariantSortOrder,
  getSavedVariantVisibleIndices,
  (filteredSavedVariants, sortOrder, visibleIndices) => {
    // Always secondary sort on xpos
    filteredSavedVariants.sort((a, b) => {
      return VARIANT_SORT_LOOKUP[sortOrder](a, b) || a.xpos - b.xpos
    })
    return filteredSavedVariants.slice(...visibleIndices)
  },
)

export const getSavedVariantTotalPages = createSelector(
  getFilteredProjectSavedVariants, getSavedVariantRecordsPerPage,
  (filteredSavedVariants, recordsPerPage) => {
    return Math.max(1, Math.ceil(filteredSavedVariants.length / recordsPerPage))
  },
)

export const getSavedVariantExportConfig = createSelector(
  getFilteredProjectSavedVariants,
  (filteredSavedVariants) => {
    const maxGenotypes = Math.max(...filteredSavedVariants.map(variant => Object.keys(variant.genotypes).length), 0)
    return {
      rawData: filteredSavedVariants,
      headers: [...Array(maxGenotypes).keys()].reduce(
        (acc, i) => [...acc, ...VARIANT_GENOTYPE_EXPORT_DATA.map(config => `${config.header}_${i + 1}`)],
        VARIANT_EXPORT_DATA.map(config => config.header),
      ),
      processRow: variant => Object.keys(variant.genotypes).reduce(
        (acc, individualId) => [...acc, ...VARIANT_GENOTYPE_EXPORT_DATA.map((config) => {
          const genotype = variant.genotypes[individualId]
          return config.getVal ? config.getVal(genotype, individualId) : genotype[config.header]
        })],
        VARIANT_EXPORT_DATA.map(config => (config.getVal ? config.getVal(variant) : variant[config.header])),
      ),
    }
  },
)

// Family table selectors
export const getProjectTableState = state => state.familyTableState
export const getProjectTablePage = state => state.familyTableState.currentPage || 1
export const getProjectTableRecordsPerPage = state => state.familyTableState.recordsPerPage || 200
export const getFamiliesFilter = state => state.familyTableState.familiesFilter || SHOW_ALL
export const getFamiliesSortOrder = state => state.familyTableState.familiesSortOrder || SORT_BY_FAMILY_NAME
export const getFamiliesSortDirection = state => state.familyTableState.familiesSortDirection || 1
export const getShowDetails = state => (state.familyTableState.showDetails !== undefined ? state.familyTableState.showDetails : true)

/**
 * function that returns an array of family guids that pass the currently-selected
 * familiesFilter.
 *
 * @param state {object} global Redux state
 */
export const getFilteredFamilies = createSelector(
  getProjectFamiliesByGuid,
  getProjectIndividualsByGuid,
  getFamiliesFilter,
  (familiesByGuid, individualsByGuid, familiesFilter) => {
    const families = Object.values(familiesByGuid)
    if (!familiesFilter || !FAMILY_FILTER_LOOKUP[familiesFilter]) {
      return families
    }

    const individuals = Object.values(individualsByGuid)
    const familyFilter = FAMILY_FILTER_LOOKUP[familiesFilter](families, individuals)
    return families.filter(familyFilter)
  },
)

/**
 * function that returns the total number of pages to show.
 *
 * @param state {object} global Redux state
 */
export const getTotalPageCount = createSelector(
  getFilteredFamilies,
  getProjectTableRecordsPerPage,
  (filteredFamilies, recordsPerPage) => {
    return Math.max(1, Math.ceil(filteredFamilies.length / recordsPerPage))
  },
)

/**
 * function that returns an array of currently-visible familyGuids based on the selected page.
 *
 * @param state {object} global Redux state
 */
export const getVisibleFamilies = createSelector(
  getFilteredFamilies,
  getProjectTablePage,
  getProjectTableRecordsPerPage,
  getTotalPageCount,
  (filteredFamilies, currentPage, recordsPerPage, totalPageCount) => {
    const page = Math.min(currentPage, totalPageCount) - 1
    return filteredFamilies.slice(page * recordsPerPage, (page + 1) * recordsPerPage)
  },
)

/**
 * function that returns an array of currently-visible family objects, sorted according to
 * currently-selected values of familiesSortOrder and familiesSortDirection.
 *
 * @param state {object} global Redux state
 */
export const getVisibleFamiliesInSortedOrder = createSelector(
  getVisibleFamilies,
  getProjectFamiliesByGuid,
  getProjectIndividualsByGuid,
  getProjectSamplesByGuid,
  getFamiliesSortOrder,
  getFamiliesSortDirection,
  (visibleFamilies, familiesByGuid, individualsByGuid, samplesByGuid, familiesSortOrder, familiesSortDirection) => {
    if (!familiesSortOrder || !FAMILY_SORT_LOOKUP[familiesSortOrder]) {
      return visibleFamilies
    }

    const getSortKey = FAMILY_SORT_LOOKUP[familiesSortOrder](familiesByGuid, individualsByGuid, samplesByGuid)

    return orderBy(visibleFamilies, [getSortKey], [familiesSortDirection > 0 ? 'asc' : 'desc'])
  },
)

/**
 * function that returns a mapping of each familyGuid to an array of individuals in that family.
 * The array of individuals is in sorted order.
 *
 * @param state {object} global Redux state
 */
export const getVisibleSortedFamiliesWithIndividuals = createSelector(
  getVisibleFamiliesInSortedOrder,
  getProjectIndividualsByGuid,
  (visibleFamilies, individualsByGuid) => {
    const AFFECTED_STATUS_ORDER = { A: 1, N: 2, U: 3 }
    const getIndivSortKey = individual => AFFECTED_STATUS_ORDER[individual.affected] || 0

    return visibleFamilies.map((family) => {
      const familyIndividuals = orderBy(family.individualGuids.map(individualGuid => individualsByGuid[individualGuid]), [getIndivSortKey])
      return Object.assign(family, { individuals: familyIndividuals })
    })
  },
)

export const getCaseReviewStatusCounts = createSelector(
  getProjectIndividualsByGuid,
  (individualsByGuid) => {
    const caseReviewStatusCounts = Object.values(individualsByGuid).reduce((acc, individual) => ({
      ...acc, [individual.caseReviewStatus]: (acc[individual.caseReviewStatus] || 0) + 1,
    }), {})

    return CASE_REVIEW_STATUS_OPTIONS.map(option => (
      { ...option, count: (caseReviewStatusCounts[option.value] || 0) }),
    )
  })

export const getAnalysisStatusCounts = createSelector(
  getProjectFamiliesByGuid,
  (familiesByGuid) => {
    const analysisStatusCounts = Object.values(familiesByGuid).reduce((acc, family) => ({
      ...acc, [family.analysisStatus]: (acc[family.analysisStatus] || 0) + 1,
    }), {})

    return FAMILY_ANALYSIS_STATUS_OPTIONS.map(option => (
      { ...option, count: (analysisStatusCounts[option.value] || 0) }),
    )
  })
<commit_msg>fixed selectEntitiesForProjectGuid handling for null values<commit_after>import orderBy from 'lodash/orderBy'
import { createSelector } from 'reselect'

import { FAMILY_ANALYSIS_STATUS_OPTIONS } from 'shared/utils/constants'

import { getProjectsByGuid, getFamiliesByGuid, getIndividualsByGuid, getSamplesByGuid } from 'redux/selectors'

import {
  SHOW_ALL,
  SORT_BY_FAMILY_NAME,
  CASE_REVIEW_STATUS_OPTIONS,
  FAMILY_FILTER_OPTIONS,
  FAMILY_SORT_OPTIONS,
  SORT_BY_FAMILY_GUID,
  VARIANT_SORT_OPTONS,
  VARIANT_EXPORT_DATA,
  VARIANT_GENOTYPE_EXPORT_DATA,
} from './constants'

const FAMILY_FILTER_LOOKUP = FAMILY_FILTER_OPTIONS.reduce(
  (acc, opt) => ({
    ...acc,
    [opt.value]: opt.createFilter,
  }), {},
)

const FAMILY_SORT_LOOKUP = FAMILY_SORT_OPTIONS.reduce(
  (acc, opt) => ({
    ...acc,
    [opt.value]: opt.createSortKeyGetter,
  }), {},
)

const VARIANT_SORT_LOOKUP = VARIANT_SORT_OPTONS.reduce(
  (acc, opt) => ({
    ...acc,
    [opt.value]: opt.comparator,
  }), {},
)

// project data selectors

export const getProjectDetailsIsLoading = state => state.projectDetailsLoading.isLoading
export const getProjectSavedVariantsIsLoading = state => state.projectSavedVariantsLoading.isLoading
export const getProjectGuid = state => state.currentProjectGuid

export const getProject = createSelector(
  getProjectsByGuid, getProjectGuid, (projectsByGuid, currentProjectGuid) => projectsByGuid[currentProjectGuid],
)

const groupEntitiesByProjectGuid = entities => Object.entries(entities).reduce((acc, [entityGuid, entity]) => {
  if (!(entity.projectGuid in acc)) {
    acc[entity.projectGuid] = {}
  }
  acc[entity.projectGuid][entityGuid] = entity

  return acc

}, {})

export const getFamiliesGroupedByProjectGuid = createSelector(getFamiliesByGuid, groupEntitiesByProjectGuid)
export const getIndividualsGroupedByProjectGuid = createSelector(getIndividualsByGuid, groupEntitiesByProjectGuid)
export const getSamplesGroupedByProjectGuid = createSelector(getSamplesByGuid, groupEntitiesByProjectGuid)

const selectEntitiesForProjectGuid = (entitiesGroupedByProjectGuid, projectGuid) => entitiesGroupedByProjectGuid[projectGuid] || {}

export const getProjectFamiliesByGuid = createSelector(getFamiliesGroupedByProjectGuid, getProjectGuid, selectEntitiesForProjectGuid)
export const getProjectIndividualsByGuid = createSelector(getIndividualsGroupedByProjectGuid, getProjectGuid, selectEntitiesForProjectGuid)
export const getProjectSamplesByGuid = createSelector(getSamplesGroupedByProjectGuid, getProjectGuid, selectEntitiesForProjectGuid)

// Saved variant selectors
export const getSavedVariantTableState = state => state.savedVariantTableState
export const getSavedVariantCategoryFilter = state => state.savedVariantTableState.categoryFilter || SHOW_ALL
export const getSavedVariantSortOrder = state => state.savedVariantTableState.sortOrder || SORT_BY_FAMILY_GUID
export const getSavedVariantHideExcluded = state => state.savedVariantTableState.hideExcluded
export const getSavedVariantCurrentPage = state => state.savedVariantTableState.currentPage || 1
export const getSavedVariantRecordsPerPage = state => state.savedVariantTableState.recordsPerPage || 25

export const getProjectSavedVariants = createSelector(
  state => state.projectSavedVariants,
  (state, props) => props.match.params,
  (projectSavedVariants, { tag, familyGuid, variantGuid }) => {
    let variants = Object.values(projectSavedVariants)
    if (variantGuid) {
      return variants.filter(o => o.variantId === variantGuid)
    }
    if (familyGuid) {
      variants = variants.filter(o => o.familyGuid === familyGuid)
    }
    return tag ? variants.filter(o => o.tags.some(t => t.name === tag)) : variants
  },
)

export const getSavedVariantVisibleIndices = createSelector(
  getSavedVariantCurrentPage, getSavedVariantRecordsPerPage,
  (currentPage, recordsPerPage) => {
    return [(currentPage - 1) * recordsPerPage, currentPage * recordsPerPage]
  },
)

export const getFilteredProjectSavedVariants = createSelector(
  getProjectSavedVariants,
  getSavedVariantCategoryFilter,
  getSavedVariantHideExcluded,
  (projectSavedVariants, categoryFilter, hideExcluded) => {
    let variantsToShow = projectSavedVariants
    if (hideExcluded) {
      variantsToShow = variantsToShow.filter(variant => variant.tags.every(t => t.name !== 'Excluded'))
    }
    if (categoryFilter !== SHOW_ALL) {
      variantsToShow = variantsToShow.filter(variant => variant.tags.some(t => t.category === categoryFilter))
    }
    return variantsToShow
  },
)

export const getVisibleSortedProjectSavedVariants = createSelector(
  getFilteredProjectSavedVariants,
  getSavedVariantSortOrder,
  getSavedVariantVisibleIndices,
  (filteredSavedVariants, sortOrder, visibleIndices) => {
    // Always secondary sort on xpos
    filteredSavedVariants.sort((a, b) => {
      return VARIANT_SORT_LOOKUP[sortOrder](a, b) || a.xpos - b.xpos
    })
    return filteredSavedVariants.slice(...visibleIndices)
  },
)

export const getSavedVariantTotalPages = createSelector(
  getFilteredProjectSavedVariants, getSavedVariantRecordsPerPage,
  (filteredSavedVariants, recordsPerPage) => {
    return Math.max(1, Math.ceil(filteredSavedVariants.length / recordsPerPage))
  },
)

export const getSavedVariantExportConfig = createSelector(
  getFilteredProjectSavedVariants,
  (filteredSavedVariants) => {
    const maxGenotypes = Math.max(...filteredSavedVariants.map(variant => Object.keys(variant.genotypes).length), 0)
    return {
      rawData: filteredSavedVariants,
      headers: [...Array(maxGenotypes).keys()].reduce(
        (acc, i) => [...acc, ...VARIANT_GENOTYPE_EXPORT_DATA.map(config => `${config.header}_${i + 1}`)],
        VARIANT_EXPORT_DATA.map(config => config.header),
      ),
      processRow: variant => Object.keys(variant.genotypes).reduce(
        (acc, individualId) => [...acc, ...VARIANT_GENOTYPE_EXPORT_DATA.map((config) => {
          const genotype = variant.genotypes[individualId]
          return config.getVal ? config.getVal(genotype, individualId) : genotype[config.header]
        })],
        VARIANT_EXPORT_DATA.map(config => (config.getVal ? config.getVal(variant) : variant[config.header])),
      ),
    }
  },
)

// Family table selectors
export const getProjectTableState = state => state.familyTableState
export const getProjectTablePage = state => state.familyTableState.currentPage || 1
export const getProjectTableRecordsPerPage = state => state.familyTableState.recordsPerPage || 200
export const getFamiliesFilter = state => state.familyTableState.familiesFilter || SHOW_ALL
export const getFamiliesSortOrder = state => state.familyTableState.familiesSortOrder || SORT_BY_FAMILY_NAME
export const getFamiliesSortDirection = state => state.familyTableState.familiesSortDirection || 1
export const getShowDetails = state => (state.familyTableState.showDetails !== undefined ? state.familyTableState.showDetails : true)

/**
 * function that returns an array of family guids that pass the currently-selected
 * familiesFilter.
 *
 * @param state {object} global Redux state
 */
export const getFilteredFamilies = createSelector(
  getProjectFamiliesByGuid,
  getProjectIndividualsByGuid,
  getFamiliesFilter,
  (familiesByGuid, individualsByGuid, familiesFilter) => {
    const families = Object.values(familiesByGuid)
    if (!familiesFilter || !FAMILY_FILTER_LOOKUP[familiesFilter]) {
      return families
    }

    const individuals = Object.values(individualsByGuid)
    const familyFilter = FAMILY_FILTER_LOOKUP[familiesFilter](families, individuals)
    return families.filter(familyFilter)
  },
)

/**
 * function that returns the total number of pages to show.
 *
 * @param state {object} global Redux state
 */
export const getTotalPageCount = createSelector(
  getFilteredFamilies,
  getProjectTableRecordsPerPage,
  (filteredFamilies, recordsPerPage) => {
    return Math.max(1, Math.ceil(filteredFamilies.length / recordsPerPage))
  },
)

/**
 * function that returns an array of currently-visible familyGuids based on the selected page.
 *
 * @param state {object} global Redux state
 */
export const getVisibleFamilies = createSelector(
  getFilteredFamilies,
  getProjectTablePage,
  getProjectTableRecordsPerPage,
  getTotalPageCount,
  (filteredFamilies, currentPage, recordsPerPage, totalPageCount) => {
    const page = Math.min(currentPage, totalPageCount) - 1
    return filteredFamilies.slice(page * recordsPerPage, (page + 1) * recordsPerPage)
  },
)

/**
 * function that returns an array of currently-visible family objects, sorted according to
 * currently-selected values of familiesSortOrder and familiesSortDirection.
 *
 * @param state {object} global Redux state
 */
export const getVisibleFamiliesInSortedOrder = createSelector(
  getVisibleFamilies,
  getProjectFamiliesByGuid,
  getProjectIndividualsByGuid,
  getProjectSamplesByGuid,
  getFamiliesSortOrder,
  getFamiliesSortDirection,
  (visibleFamilies, familiesByGuid, individualsByGuid, samplesByGuid, familiesSortOrder, familiesSortDirection) => {
    if (!familiesSortOrder || !FAMILY_SORT_LOOKUP[familiesSortOrder]) {
      return visibleFamilies
    }

    const getSortKey = FAMILY_SORT_LOOKUP[familiesSortOrder](familiesByGuid, individualsByGuid, samplesByGuid)

    return orderBy(visibleFamilies, [getSortKey], [familiesSortDirection > 0 ? 'asc' : 'desc'])
  },
)

/**
 * function that returns a mapping of each familyGuid to an array of individuals in that family.
 * The array of individuals is in sorted order.
 *
 * @param state {object} global Redux state
 */
export const getVisibleSortedFamiliesWithIndividuals = createSelector(
  getVisibleFamiliesInSortedOrder,
  getProjectIndividualsByGuid,
  (visibleFamilies, individualsByGuid) => {
    const AFFECTED_STATUS_ORDER = { A: 1, N: 2, U: 3 }
    const getIndivSortKey = individual => AFFECTED_STATUS_ORDER[individual.affected] || 0

    return visibleFamilies.map((family) => {
      const familyIndividuals = orderBy(family.individualGuids.map(individualGuid => individualsByGuid[individualGuid]), [getIndivSortKey])
      return Object.assign(family, { individuals: familyIndividuals })
    })
  },
)

export const getCaseReviewStatusCounts = createSelector(
  getProjectIndividualsByGuid,
  (individualsByGuid) => {
    const caseReviewStatusCounts = Object.values(individualsByGuid).reduce((acc, individual) => ({
      ...acc, [individual.caseReviewStatus]: (acc[individual.caseReviewStatus] || 0) + 1,
    }), {})

    return CASE_REVIEW_STATUS_OPTIONS.map(option => (
      { ...option, count: (caseReviewStatusCounts[option.value] || 0) }),
    )
  })

export const getAnalysisStatusCounts = createSelector(
  getProjectFamiliesByGuid,
  (familiesByGuid) => {
    const analysisStatusCounts = Object.values(familiesByGuid).reduce((acc, family) => ({
      ...acc, [family.analysisStatus]: (acc[family.analysisStatus] || 0) + 1,
    }), {})

    return FAMILY_ANALYSIS_STATUS_OPTIONS.map(option => (
      { ...option, count: (analysisStatusCounts[option.value] || 0) }),
    )
  })
<|endoftext|>


<commit_before>// @flow
import * as React from 'react'
import {Section, PushButtonCell} from '@frogpond/tableview'
import {type NavigationScreenProp} from 'react-navigation'

type Props = {navigation: NavigationScreenProp<*>}

export class DeveloperSection extends React.Component<Props> {
	onAPIButton = () => this.props.navigation.navigate('APITestView')
	onBonAppButton = () => this.props.navigation.navigate('BonAppPickerView')
	onDebugButton = () => this.props.navigation.navigate('DebugView')

	render() {
		return (

				<PushButtonCell onPress={this.onAPIButton} title="API Tester" />
				<PushButtonCell
					onPress={this.onBonAppButton}
					title="Bon Appetit Picker"
				/>
				<PushButtonCell onPress={this.onDebugButton} title="Debug" />

		)
	}
}
<commit_msg>Add debug-menu manual triggers for Sentry<commit_after>// @flow
import {Sentry, SentrySeverity} from 'react-native-sentry'
import * as React from 'react'
import {Section, PushButtonCell} from '@frogpond/tableview'
import {type NavigationScreenProp} from 'react-navigation'

type Props = {navigation: NavigationScreenProp<*>}

export class DeveloperSection extends React.Component<Props> {
	onAPIButton = () => this.props.navigation.navigate('APITestView')
	onBonAppButton = () => this.props.navigation.navigate('BonAppPickerView')
	onDebugButton = () => this.props.navigation.navigate('DebugView')
	sendSentryMessage = () => {
		Sentry.captureMessage('A Sentry Message', {
			level: SentrySeverity.Info,
		})
	}
	sendSentryException = () => {
		Sentry.captureException(new Error('Debug Exception'))
	}

	render() {
		return (

				<PushButtonCell onPress={this.onAPIButton} title="API Tester" />
				<PushButtonCell
					onPress={this.onBonAppButton}
					title="Bon Appetit Picker"
				/>
				<PushButtonCell onPress={this.onDebugButton} title="Debug" />
				<PushButtonCell
					onPress={this.sendSentryMessage}
					title="Send a Sentry Message"
				/>
				<PushButtonCell
					onPress={this.sendSentryException}
					title="Send a Sentry Exception"
				/>

		)
	}
}
<|endoftext|>


<commit_before>import { client } from '../plugins/Platform.js'
import { getModifierDirections, shouldStart } from '../utils/private/touch.js'
import { addEvt, cleanEvt, position, leftClick, prevent, stop, stopAndPrevent, preventDraggable, noop } from '../utils/event.js'
import { clearSelection } from '../utils/private/selection.js'

function getChanges (evt, ctx, isFinal) {
  const pos = position(evt)
  let
    dir,
    distX = pos.left - ctx.event.x,
    distY = pos.top - ctx.event.y,
    absX = Math.abs(distX),
    absY = Math.abs(distY)

  const direction = ctx.direction

  if (direction.horizontal === true && direction.vertical !== true) {
    dir = distX < 0 ? 'left' : 'right'
  }
  else if (direction.horizontal !== true && direction.vertical === true) {
    dir = distY < 0 ? 'up' : 'down'
  }
  else if (direction.up === true && distY < 0) {
    dir = 'up'
    if (absX > absY) {
      if (direction.left === true && distX < 0) {
        dir = 'left'
      }
      else if (direction.right === true && distX > 0) {
        dir = 'right'
      }
    }
  }
  else if (direction.down === true && distY > 0) {
    dir = 'down'
    if (absX > absY) {
      if (direction.left === true && distX < 0) {
        dir = 'left'
      }
      else if (direction.right === true && distX > 0) {
        dir = 'right'
      }
    }
  }
  else if (direction.left === true && distX < 0) {
    dir = 'left'
    if (absX < absY) {
      if (direction.up === true && distY < 0) {
        dir = 'up'
      }
      else if (direction.down === true && distY > 0) {
        dir = 'down'
      }
    }
  }
  else if (direction.right === true && distX > 0) {
    dir = 'right'
    if (absX < absY) {
      if (direction.up === true && distY < 0) {
        dir = 'up'
      }
      else if (direction.down === true && distY > 0) {
        dir = 'down'
      }
    }
  }

  let synthetic = false

  if (dir === void 0 && isFinal === false) {
    if (ctx.event.isFirst === true || ctx.event.lastDir === void 0) {
      return {}
    }

    dir = ctx.event.lastDir
    synthetic = true

    if (dir === 'left' || dir === 'right') {
      pos.left -= distX
      absX = 0
      distX = 0
    }
    else {
      pos.top -= distY
      absY = 0
      distY = 0
    }
  }

  return {
    synthetic,
    payload: {
      evt,
      touch: ctx.event.mouse !== true,
      mouse: ctx.event.mouse === true,
      position: pos,
      direction: dir,
      isFirst: ctx.event.isFirst,
      isFinal: isFinal === true,
      duration: Date.now() - ctx.event.time,
      distance: {
        x: absX,
        y: absY
      },
      offset: {
        x: distX,
        y: distY
      },
      delta: {
        x: pos.left - ctx.event.lastX,
        y: pos.top - ctx.event.lastY
      }
    }
  }
}

function destroy (el) {
  const ctx = el.__qtouchpan
  if (ctx !== void 0) {
    // emit the end event when the directive is destroyed while active
    // this is only needed in TouchPan because the rest of the touch directives do not emit an end event
    // the condition is also checked in the start of function but we avoid the call
    ctx.event !== void 0 && ctx.end()

    cleanEvt(ctx, 'main')
    cleanEvt(ctx, 'temp')

    client.is.firefox === true && preventDraggable(el, false)
    ctx.styleCleanup !== void 0 && ctx.styleCleanup()

    delete el.__qtouchpan
  }
}

let uid = 0

export default {
  name: 'touch-pan',

  bind (el, { value, modifiers }) {
    if (el.__qtouchpan !== void 0) {
      destroy(el)
      el.__qtouchpan_destroyed = true
    }

    // early return, we don't need to do anything
    if (modifiers.mouse !== true && client.has.touch !== true) {
      return
    }

    function handleEvent (evt, mouseEvent) {
      if (modifiers.mouse === true && mouseEvent === true) {
        stopAndPrevent(evt)
      }
      else {
        modifiers.stop === true && stop(evt)
        modifiers.prevent === true && prevent(evt)
      }
    }

    const ctx = {
      uid: 'qvtp_' + (uid++),
      handler: value,
      modifiers,
      direction: getModifierDirections(modifiers),

      noop,

      mouseStart (evt) {
        if (shouldStart(evt, ctx) && leftClick(evt)) {
          addEvt(ctx, 'temp', [
            [ document, 'mousemove', 'move', 'notPassiveCapture' ],
            [ document, 'mouseup', 'end', 'passiveCapture' ]
          ])

          ctx.start(evt, true)
        }
      },

      touchStart (evt) {
        if (shouldStart(evt, ctx)) {
          const target = evt.target

          addEvt(ctx, 'temp', [
            [ target, 'touchmove', 'move', 'notPassiveCapture' ],
            [ target, 'touchcancel', 'end', 'passiveCapture' ],
            [ target, 'touchend', 'end', 'passiveCapture' ]
          ])

          ctx.start(evt)
        }
      },

      start (evt, mouseEvent) {
        client.is.firefox === true && preventDraggable(el, true)
        ctx.lastEvt = evt

        const pos = position(evt)

        /*
         * Stop propagation so possible upper v-touch-pan don't catch this as well;
         * If we're not the target (based on modifiers), we'll re-emit the event later
         */
        if (mouseEvent === true || modifiers.stop === true) {
          /*
           * are we directly switching to detected state?
           * clone event only otherwise
           */
          if (
            ctx.direction.all !== true &&
            (mouseEvent !== true || ctx.direction.mouseAllDir !== true)
          ) {
            const clone = evt.type.indexOf('mouse') > -1
              ? new MouseEvent(evt.type, evt)
              : new TouchEvent(evt.type, evt)

            evt.defaultPrevented === true && prevent(clone)
            evt.cancelBubble === true && stop(clone)

            clone.qClonedBy = evt.qClonedBy === void 0
              ? [ctx.uid]
              : evt.qClonedBy.concat(ctx.uid)
            clone.qKeyEvent = evt.qKeyEvent
            clone.qClickOutside = evt.qClickOutside

            ctx.initialEvent = {
              target: evt.target,
              event: clone
            }
          }

          stop(evt)
        }

        ctx.event = {
          x: pos.left,
          y: pos.top,
          time: Date.now(),
          mouse: mouseEvent === true,
          detected: false,
          isFirst: true,
          isFinal: false,
          lastX: pos.left,
          lastY: pos.top
        }
      },

      move (evt) {
        if (ctx.event === void 0) {
          return
        }

        ctx.lastEvt = evt

        const isMouseEvt = ctx.event.mouse === true
        const start = () => {
          handleEvent(evt, isMouseEvt)

          if (modifiers.preserveCursor !== true) {
            document.documentElement.style.cursor = 'grabbing'
          }
          isMouseEvt === true && document.body.classList.add('no-pointer-events--children')
          document.body.classList.add('non-selectable')
          clearSelection()

          ctx.styleCleanup = withDelayedFn => {
            ctx.styleCleanup = void 0

            if (modifiers.preserveCursor !== true) {
              document.documentElement.style.cursor = ''
            }
            document.body.classList.remove('non-selectable')

            if (isMouseEvt === true) {
              const remove = () => {
                document.body.classList.remove('no-pointer-events--children')
              }

              if (withDelayedFn !== void 0) {
                setTimeout(() => {
                  remove()
                  withDelayedFn()
                }, 50)
              }
              else { remove() }
            }
            else if (withDelayedFn !== void 0) {
              withDelayedFn()
            }
          }
        }

        if (ctx.event.detected === true) {
          ctx.event.isFirst !== true && handleEvent(evt, ctx.event.mouse)

          const { payload, synthetic } = getChanges(evt, ctx, false)

          if (payload !== void 0) {
            if (ctx.handler(payload) === false) {
              ctx.end(evt)
            }
            else {
              if (ctx.styleCleanup === void 0 && ctx.event.isFirst === true) {
                start()
              }

              ctx.event.lastX = payload.position.left
              ctx.event.lastY = payload.position.top
              ctx.event.lastDir = synthetic === true ? void 0 : payload.direction
              ctx.event.isFirst = false
            }
          }

          return
        }

        if (
          ctx.direction.all === true ||
          (isMouseEvt === true && ctx.modifiers.mouseAllDir === true)
        ) {
          start()
          ctx.event.detected = true
          ctx.move(evt)
          return
        }

        const
          pos = position(evt),
          distX = pos.left - ctx.event.x,
          distY = pos.top - ctx.event.y,
          absX = Math.abs(distX),
          absY = Math.abs(distY)

        if (absX !== absY) {
          if (
            (ctx.direction.horizontal === true && absX > absY) ||
            (ctx.direction.vertical === true && absX < absY) ||
            (ctx.direction.up === true && absX < absY && distY < 0) ||
            (ctx.direction.down === true && absX < absY && distY > 0) ||
            (ctx.direction.left === true && absX > absY && distX < 0) ||
            (ctx.direction.right === true && absX > absY && distX > 0)
          ) {
            ctx.event.detected = true
            ctx.move(evt)
          }
          else {
            ctx.end(evt, true)
          }
        }
      },

      end (evt, abort) {
        if (ctx.event === void 0) {
          return
        }

        cleanEvt(ctx, 'temp')
        client.is.firefox === true && preventDraggable(el, false)

        if (abort === true) {
          ctx.styleCleanup !== void 0 && ctx.styleCleanup()

          if (ctx.event.detected !== true && ctx.initialEvent !== void 0) {
            ctx.initialEvent.target.dispatchEvent(ctx.initialEvent.event)
          }
        }
        else if (ctx.event.detected === true) {
          ctx.event.isFirst === true && ctx.handler(getChanges(evt === void 0 ? ctx.lastEvt : evt, ctx).payload)

          const { payload } = getChanges(evt === void 0 ? ctx.lastEvt : evt, ctx, true)
          const fn = () => { ctx.handler(payload) }

          if (ctx.styleCleanup !== void 0) {
            ctx.styleCleanup(fn)
          }
          else {
            fn()
          }
        }

        ctx.event = void 0
        ctx.initialEvent = void 0
        ctx.lastEvt = void 0
      }
    }

    el.__qtouchpan = ctx

    modifiers.mouse === true && addEvt(ctx, 'main', [
      [ el, 'mousedown', 'mouseStart', `passive${modifiers.mouseCapture === true ? 'Capture' : ''}` ]
    ])

    client.has.touch === true && addEvt(ctx, 'main', [
      [ el, 'touchstart', 'touchStart', `passive${modifiers.capture === true ? 'Capture' : ''}` ],
      [ el, 'touchmove', 'noop', 'notPassiveCapture' ]
    ])
  },

  update (el, { oldValue, value }) {
    const ctx = el.__qtouchpan
    if (ctx !== void 0 && oldValue !== value) {
      typeof value !== 'function' && ctx.end()
      ctx.handler = value
    }
  },

  unbind (el) {
    if (el.__qtouchpan_destroyed === void 0) {
      destroy(el)
    }
    else {
      delete el.__qtouchpan_destroyed
    }
  }
}
<commit_msg>fix(TouchPan): typo in modifiers mouseAllDirs (#14520)<commit_after>import { client } from '../plugins/Platform.js'
import { getModifierDirections, shouldStart } from '../utils/private/touch.js'
import { addEvt, cleanEvt, position, leftClick, prevent, stop, stopAndPrevent, preventDraggable, noop } from '../utils/event.js'
import { clearSelection } from '../utils/private/selection.js'

function getChanges (evt, ctx, isFinal) {
  const pos = position(evt)
  let
    dir,
    distX = pos.left - ctx.event.x,
    distY = pos.top - ctx.event.y,
    absX = Math.abs(distX),
    absY = Math.abs(distY)

  const direction = ctx.direction

  if (direction.horizontal === true && direction.vertical !== true) {
    dir = distX < 0 ? 'left' : 'right'
  }
  else if (direction.horizontal !== true && direction.vertical === true) {
    dir = distY < 0 ? 'up' : 'down'
  }
  else if (direction.up === true && distY < 0) {
    dir = 'up'
    if (absX > absY) {
      if (direction.left === true && distX < 0) {
        dir = 'left'
      }
      else if (direction.right === true && distX > 0) {
        dir = 'right'
      }
    }
  }
  else if (direction.down === true && distY > 0) {
    dir = 'down'
    if (absX > absY) {
      if (direction.left === true && distX < 0) {
        dir = 'left'
      }
      else if (direction.right === true && distX > 0) {
        dir = 'right'
      }
    }
  }
  else if (direction.left === true && distX < 0) {
    dir = 'left'
    if (absX < absY) {
      if (direction.up === true && distY < 0) {
        dir = 'up'
      }
      else if (direction.down === true && distY > 0) {
        dir = 'down'
      }
    }
  }
  else if (direction.right === true && distX > 0) {
    dir = 'right'
    if (absX < absY) {
      if (direction.up === true && distY < 0) {
        dir = 'up'
      }
      else if (direction.down === true && distY > 0) {
        dir = 'down'
      }
    }
  }

  let synthetic = false

  if (dir === void 0 && isFinal === false) {
    if (ctx.event.isFirst === true || ctx.event.lastDir === void 0) {
      return {}
    }

    dir = ctx.event.lastDir
    synthetic = true

    if (dir === 'left' || dir === 'right') {
      pos.left -= distX
      absX = 0
      distX = 0
    }
    else {
      pos.top -= distY
      absY = 0
      distY = 0
    }
  }

  return {
    synthetic,
    payload: {
      evt,
      touch: ctx.event.mouse !== true,
      mouse: ctx.event.mouse === true,
      position: pos,
      direction: dir,
      isFirst: ctx.event.isFirst,
      isFinal: isFinal === true,
      duration: Date.now() - ctx.event.time,
      distance: {
        x: absX,
        y: absY
      },
      offset: {
        x: distX,
        y: distY
      },
      delta: {
        x: pos.left - ctx.event.lastX,
        y: pos.top - ctx.event.lastY
      }
    }
  }
}

function destroy (el) {
  const ctx = el.__qtouchpan
  if (ctx !== void 0) {
    // emit the end event when the directive is destroyed while active
    // this is only needed in TouchPan because the rest of the touch directives do not emit an end event
    // the condition is also checked in the start of function but we avoid the call
    ctx.event !== void 0 && ctx.end()

    cleanEvt(ctx, 'main')
    cleanEvt(ctx, 'temp')

    client.is.firefox === true && preventDraggable(el, false)
    ctx.styleCleanup !== void 0 && ctx.styleCleanup()

    delete el.__qtouchpan
  }
}

let uid = 0

export default {
  name: 'touch-pan',

  bind (el, { value, modifiers }) {
    if (el.__qtouchpan !== void 0) {
      destroy(el)
      el.__qtouchpan_destroyed = true
    }

    // early return, we don't need to do anything
    if (modifiers.mouse !== true && client.has.touch !== true) {
      return
    }

    function handleEvent (evt, mouseEvent) {
      if (modifiers.mouse === true && mouseEvent === true) {
        stopAndPrevent(evt)
      }
      else {
        modifiers.stop === true && stop(evt)
        modifiers.prevent === true && prevent(evt)
      }
    }

    const ctx = {
      uid: 'qvtp_' + (uid++),
      handler: value,
      modifiers,
      direction: getModifierDirections(modifiers),

      noop,

      mouseStart (evt) {
        if (shouldStart(evt, ctx) && leftClick(evt)) {
          addEvt(ctx, 'temp', [
            [ document, 'mousemove', 'move', 'notPassiveCapture' ],
            [ document, 'mouseup', 'end', 'passiveCapture' ]
          ])

          ctx.start(evt, true)
        }
      },

      touchStart (evt) {
        if (shouldStart(evt, ctx)) {
          const target = evt.target

          addEvt(ctx, 'temp', [
            [ target, 'touchmove', 'move', 'notPassiveCapture' ],
            [ target, 'touchcancel', 'end', 'passiveCapture' ],
            [ target, 'touchend', 'end', 'passiveCapture' ]
          ])

          ctx.start(evt)
        }
      },

      start (evt, mouseEvent) {
        client.is.firefox === true && preventDraggable(el, true)
        ctx.lastEvt = evt

        const pos = position(evt)

        /*
         * Stop propagation so possible upper v-touch-pan don't catch this as well;
         * If we're not the target (based on modifiers), we'll re-emit the event later
         */
        if (mouseEvent === true || modifiers.stop === true) {
          /*
           * are we directly switching to detected state?
           * clone event only otherwise
           */
          if (
            ctx.direction.all !== true &&
            (mouseEvent !== true || ctx.modifiers.mouseAllDir !== true)
          ) {
            const clone = evt.type.indexOf('mouse') > -1
              ? new MouseEvent(evt.type, evt)
              : new TouchEvent(evt.type, evt)

            evt.defaultPrevented === true && prevent(clone)
            evt.cancelBubble === true && stop(clone)

            clone.qClonedBy = evt.qClonedBy === void 0
              ? [ctx.uid]
              : evt.qClonedBy.concat(ctx.uid)
            clone.qKeyEvent = evt.qKeyEvent
            clone.qClickOutside = evt.qClickOutside

            ctx.initialEvent = {
              target: evt.target,
              event: clone
            }
          }

          stop(evt)
        }

        ctx.event = {
          x: pos.left,
          y: pos.top,
          time: Date.now(),
          mouse: mouseEvent === true,
          detected: false,
          isFirst: true,
          isFinal: false,
          lastX: pos.left,
          lastY: pos.top
        }
      },

      move (evt) {
        if (ctx.event === void 0) {
          return
        }

        ctx.lastEvt = evt

        const isMouseEvt = ctx.event.mouse === true
        const start = () => {
          handleEvent(evt, isMouseEvt)

          if (modifiers.preserveCursor !== true) {
            document.documentElement.style.cursor = 'grabbing'
          }
          isMouseEvt === true && document.body.classList.add('no-pointer-events--children')
          document.body.classList.add('non-selectable')
          clearSelection()

          ctx.styleCleanup = withDelayedFn => {
            ctx.styleCleanup = void 0

            if (modifiers.preserveCursor !== true) {
              document.documentElement.style.cursor = ''
            }
            document.body.classList.remove('non-selectable')

            if (isMouseEvt === true) {
              const remove = () => {
                document.body.classList.remove('no-pointer-events--children')
              }

              if (withDelayedFn !== void 0) {
                setTimeout(() => {
                  remove()
                  withDelayedFn()
                }, 50)
              }
              else { remove() }
            }
            else if (withDelayedFn !== void 0) {
              withDelayedFn()
            }
          }
        }

        if (ctx.event.detected === true) {
          ctx.event.isFirst !== true && handleEvent(evt, ctx.event.mouse)

          const { payload, synthetic } = getChanges(evt, ctx, false)

          if (payload !== void 0) {
            if (ctx.handler(payload) === false) {
              ctx.end(evt)
            }
            else {
              if (ctx.styleCleanup === void 0 && ctx.event.isFirst === true) {
                start()
              }

              ctx.event.lastX = payload.position.left
              ctx.event.lastY = payload.position.top
              ctx.event.lastDir = synthetic === true ? void 0 : payload.direction
              ctx.event.isFirst = false
            }
          }

          return
        }

        if (
          ctx.direction.all === true ||
          (isMouseEvt === true && ctx.modifiers.mouseAllDir === true)
        ) {
          start()
          ctx.event.detected = true
          ctx.move(evt)
          return
        }

        const
          pos = position(evt),
          distX = pos.left - ctx.event.x,
          distY = pos.top - ctx.event.y,
          absX = Math.abs(distX),
          absY = Math.abs(distY)

        if (absX !== absY) {
          if (
            (ctx.direction.horizontal === true && absX > absY) ||
            (ctx.direction.vertical === true && absX < absY) ||
            (ctx.direction.up === true && absX < absY && distY < 0) ||
            (ctx.direction.down === true && absX < absY && distY > 0) ||
            (ctx.direction.left === true && absX > absY && distX < 0) ||
            (ctx.direction.right === true && absX > absY && distX > 0)
          ) {
            ctx.event.detected = true
            ctx.move(evt)
          }
          else {
            ctx.end(evt, true)
          }
        }
      },

      end (evt, abort) {
        if (ctx.event === void 0) {
          return
        }

        cleanEvt(ctx, 'temp')
        client.is.firefox === true && preventDraggable(el, false)

        if (abort === true) {
          ctx.styleCleanup !== void 0 && ctx.styleCleanup()

          if (ctx.event.detected !== true && ctx.initialEvent !== void 0) {
            ctx.initialEvent.target.dispatchEvent(ctx.initialEvent.event)
          }
        }
        else if (ctx.event.detected === true) {
          ctx.event.isFirst === true && ctx.handler(getChanges(evt === void 0 ? ctx.lastEvt : evt, ctx).payload)

          const { payload } = getChanges(evt === void 0 ? ctx.lastEvt : evt, ctx, true)
          const fn = () => { ctx.handler(payload) }

          if (ctx.styleCleanup !== void 0) {
            ctx.styleCleanup(fn)
          }
          else {
            fn()
          }
        }

        ctx.event = void 0
        ctx.initialEvent = void 0
        ctx.lastEvt = void 0
      }
    }

    el.__qtouchpan = ctx

    modifiers.mouse === true && addEvt(ctx, 'main', [
      [ el, 'mousedown', 'mouseStart', `passive${modifiers.mouseCapture === true ? 'Capture' : ''}` ]
    ])

    client.has.touch === true && addEvt(ctx, 'main', [
      [ el, 'touchstart', 'touchStart', `passive${modifiers.capture === true ? 'Capture' : ''}` ],
      [ el, 'touchmove', 'noop', 'notPassiveCapture' ]
    ])
  },

  update (el, { oldValue, value }) {
    const ctx = el.__qtouchpan
    if (ctx !== void 0 && oldValue !== value) {
      typeof value !== 'function' && ctx.end()
      ctx.handler = value
    }
  },

  unbind (el) {
    if (el.__qtouchpan_destroyed === void 0) {
      destroy(el)
    }
    else {
      delete el.__qtouchpan_destroyed
    }
  }
}
<|endoftext|>


<commit_before>const path = require('path');
const utils = require('./utils');

const FLEXIBILITY = 0.5;
const METRICS = ['statements', 'branches', 'functions', 'lines'];

function getCoverageSummaryPath(coveragePath) {
  const coverageDir = path.join(__dirname, coveragePath);
  const directories = utils.getDirectories(coverageDir);
  directories.filter(dir => !/src|node_modules/.test(dir));
  const coverageSummaryDir = directories.length ? directories[0] : '';
  return path.join(coveragePath, coverageSummaryDir, 'coverage-summary.json');
}

function getCoverageSummaryTotal(coverageSummary, flexibility) {
  const res = {};
  METRICS.forEach((key) => {
    res[key] = coverageSummary.total ? coverageSummary.total[key].pct - flexibility : 0;
  });
  return res;
}

function updateCodeCoverage(options) {
  const optionsRef = options || {};
  const coveragePath = optionsRef.coveragePath || 'coverage';
  const coverageCheckPath = optionsRef.coverageCheckPath || 'coverage.conf.json';
  const flexibility = optionsRef.flexibility || FLEXIBILITY;

  const coverageSummaryPath = getCoverageSummaryPath(coveragePath);
  const coverageSummary = utils.getJson(coverageSummaryPath);

  const coverageCheck = getCoverageSummaryTotal(coverageSummary, flexibility);

  const isYaml = /\.yml$/.test(coverageCheckPath);
  if (isYaml) {
    utils.writeYaml(coverageCheckPath, coverageCheck);
  } else {
    utils.writeJson(coverageCheckPath, coverageCheck);
  }

  console.log('coverage-updater: updating "', coverageCheckPath, '":\n', coverageCheck);
}

module.exports = {
  getCoverageSummaryPath,
  getCoverageSummaryTotal,
  updateCodeCoverage,
};
<commit_msg>refactor: add calcCheck()<commit_after>const path = require('path');
const utils = require('./utils');

const FLEXIBILITY = 0.5;
const METRICS = ['statements', 'branches', 'functions', 'lines'];

function getCoverageSummaryPath(coveragePath) {
  const coverageDir = path.join(__dirname, coveragePath);
  const directories = utils.getDirectories(coverageDir);
  directories.filter(dir => !/src|node_modules/.test(dir));
  const coverageSummaryDir = directories.length ? directories[0] : '';
  return path.join(coveragePath, coverageSummaryDir, 'coverage-summary.json');
}

function getCoverageSummaryTotal(coverageSummary) {
  const res = {};
  METRICS.forEach((key) => {
    res[key] = coverageSummary.total ? coverageSummary.total[key].pct : 0;
  });
  return res;
}

function calcCheck(newCheck, flexibility, oldCheck) {
  const res = {};
  Object.keys(newCheck).forEach((key) => {
    const newTh = newCheck[key] - flexibility || 0;
    const oldTh = oldCheck[key] || 0;
    res[key] = newTh > oldTh ? newTh : oldTh;
  });
  return res;
}

function updateCodeCoverage(options) {
  const optionsRef = options || {};
  const coveragePath = optionsRef.coveragePath || 'coverage';
  const coverageCheckPath = optionsRef.coverageCheckPath || 'coverage.conf.json';
  const flexibility = optionsRef.flexibility || FLEXIBILITY;

  const coverageSummaryPath = getCoverageSummaryPath(coveragePath);
  const coverageSummary = utils.getJson(coverageSummaryPath);

  const coverageCheck = getCoverageSummaryTotal(coverageSummary);

  const check = calcCheck(coverageCheck, {}, flexibility);

  const isYaml = /\.yml$/.test(check);
  if (isYaml) {
    utils.writeYaml(coverageCheckPath, check);
  } else {
    utils.writeJson(coverageCheckPath, check);
  }

  console.log('coverage-updater: updating "', coverageCheckPath, '":\n', coverageCheck);
}

module.exports = {
  getCoverageSummaryPath,
  getCoverageSummaryTotal,
  calcCheck,
  updateCodeCoverage,
};
<|endoftext|>


<commit_before>/*
 Copyright 2012 - $Date $ by PeopleWare n.v.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

define(["dojo/_base/declare", "./PersistentObject", "ppwcode-util-oddsAndEnds/js", "dojo/date/stamp",
        "ppwcode-util-oddsAndEnds/log/logger!", "module"],
    function(declare, PersistentObject, js, dateStamp,
             logger, module) {

      function stringToDate(candidate) {
        //noinspection FallthroughInSwitchStatementJS
        switch (js.typeOf(candidate)) {
          case "undefined":
          case "null":
            return null;
          case "date":
            return candidate;
          case "string":
            return dateStamp.fromISOString(candidate);
          default:
            logger.error("cannot convert to date: ", candidate, js.typeOf(candidate));
            throw "ERROR: cannot convert to date: " + candidate;
        }
      }

      function compareDate(d1, d2) {
        // summary:
        //   We use < 1000 (1s) instead of < 0, because the server stores dates only to the second.
        //   With the current server implementation, we see that if we retrieve quickly after an
        //   update or create, the retrieve lastModified at is later than the one in the response
        //   of the action. The reason is, that the data in the action response comes from RAM,
        //   and has values up to the nanosecond or so (23/6/2012 15:45:32.424242242), while the
        //   data in the retrieve comes from the DB, which only stores up to the second
        //   (23/6/2012 15:45:32). So, it seems that retrieve-date is earlier than the action
        //   response date, which is impossible. By giving our comparison a 1 second leeway,
        //   this is resolved.

        if (d1 === d2) {
          return 0;
        }
        if (!d1) {
          return -1;
        }
        if (!d2) {
          return +1;
        }
        var delta = d1.getTime() - d2.getTime();
        if (delta < -1000) {
          return -1;
        }
        if (delta > +1000) {
          return +1;
        }
        return 0;
      }

      // MUDO There is a much worse problem: a RAM-created server Date is in the local time of the server
      //      While the time in the DB has no timezone. A RAM-created server Date that is sent over JSON
      //      contains timezone information. A date that is retrieved from the DB does not contain
      //      timezone in RAM, and thus does also not contain timezone information when it is sent over
      //      JSON.
      //      When JavaScript parses a string with timezone information, it takes it into account.
      //      When JavaScript parses a string without timezone information, it assumes the local
      //      timezone.
      //      When server and client are in a different timezone, when the JSON contains timezone information,
      //      the string is interpreted in the timezone of the server. When the JSON does not contain
      //      timezone information, the string is interpreted in the timezone of the client.
      //      For webapplications, this in unacceptable.
      // MUDO for now we work around this by NOT SENDING THE DATA TO THE SERVER IN THE FIRST PLACE
      //      that was another workaround; the effect of this is that we will not see this data after update
      // MUDO but it is obviously wrong

      // MUDO Attempted workaround for PICTOPERFECT-484: send the data to the server

      var InsertAuditableObject = declare([PersistentObject], {
        // InsertAuditableObjects have a `createdAt` and `createdBy` property, which is set by the server.
        // These properties cannot be set in the UI, and are initially null. Once set, the server should always
        // return the same values, except after delete, when they all turn null together again.

        _c_invar: [
          function() {return this._c_prop_string("createdBy");},
          function() {return this._c_prop_date("createdAt");},
          /*
            createdAt must be in the past
            but we cannot test that: server time and time of this local computer are incomparable
           */
          function() {return !!this.get("persistenceId") === !!this.get("createdBy");}, // both exist together or not
          function() {return !!this.get("createdBy") === !!this.get("createdAt");} // both exist together or not
        ],

        // createdBy: String
        createdBy: null,

        // createdAt: Date
        createdAt: null,

        _createdAtSetter: function() {
          // createdAt is read-only
          throw "ERROR createdAt is read-only";
        },

        _createdBySetter: function() {
          // createdBy is read-only
          throw "ERROR createdBy is read-only";
        },

        reload: function(/*Object*/ json) {
          // created.. can change from null to an actual date and username number after create,
          this._c_pre(function() {return json;});
// MUDO PICTOPERFECT-482          this._c_pre(function() {return !!json.persistenceId === !!json.createdBy;});
          this._c_pre(function() {return !!json.createdBy === !!json.createdAt;});
          this._c_pre(function() {return this._c_prop_string(json, "createdBy");});
          /*
          this._c_pre(function() {return !this.get("persistenceId") || !json.persistenceId || (this.get("createdBy") === json.createdBy);});
          The above is true, from the outside, but we cannot test it here, with this contract framework, because persistenceId is set
          by the superclass from null to the new value (on create) before we reach this test. It was true before reload, but not
          halfway. What follows is an equivalent local version.
          */
// MUDO PICTOPERFECT-482          this._c_pre(function() {return !this.get("createdBy") || !json.persistenceId || (this.get("createdBy") === json.createdBy);});

          logger.debug("Trying to convert to date: ", json.createdAt);

          this._c_pre(function() {return this._c_prop_string(json, "createdAt") || this._c_prop_date(json, "createdAt");});
          this._c_pre(function() {return !this.get("createdBy") || !json.persistenceId || compareDate(this.get("createdAt"), stringToDate(json.createdAt)) === 0;});
          if (!this.get("createdBy") || !json.createdBy) {
            this._changeAttrValue("createdBy", json.createdBy);
            this._changeAttrValue("createdAt", stringToDate(json.createdAt));
          }
        },

        // it makes no senses whatsoever to send this data back to the back-end
        // MUDO Workaround for PICTOPERFECT-484: send the data to the server; this does eem to do the trick; is the timezone info above no longer applicable?

        _extendJsonObject:function (/*Object*/ json) {
          json.createdBy = this.get("createdBy");
          json.createdAt = this.get("createdAt");
        },

        _stateToString: function(/*String[]*/ toStrings) {
          toStrings.push("createdAt: " + this.createdAt);
          toStrings.push("createdBy: " + this.createdBy);
        }
      });

      InsertAuditableObject.mid = module.id;
      InsertAuditableObject.stringToDate = stringToDate;
      InsertAuditableObject.compareDate = compareDate;
      return InsertAuditableObject;
    }
);
<commit_msg>Does this workaround work? eopdf is WEIRD<commit_after>/*
 Copyright 2012 - $Date $ by PeopleWare n.v.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

define(["dojo/_base/declare", "./PersistentObject", "ppwcode-util-oddsAndEnds/js", "dojo/date/stamp",
        "ppwcode-util-oddsAndEnds/log/logger!", "module"],
    function(declare, PersistentObject, js, dateStamp,
             logger, module) {

      function stringToDate(candidate) {
        if (!candidate) {
          // workaround; it seems that js.typeOf(null) is returned by eopdf as "domwindow"
          return null;
        }
        //noinspection FallthroughInSwitchStatementJS
        switch (js.typeOf(candidate)) {
          case "undefined":
          case "null":
            return null;
          case "date":
            return candidate;
          case "string":
            return dateStamp.fromISOString(candidate);
          default:
            logger.error("cannot convert to date: ", candidate, js.typeOf(candidate));
            throw "ERROR: cannot convert to date: " + candidate;
        }
      }

      function compareDate(d1, d2) {
        // summary:
        //   We use < 1000 (1s) instead of < 0, because the server stores dates only to the second.
        //   With the current server implementation, we see that if we retrieve quickly after an
        //   update or create, the retrieve lastModified at is later than the one in the response
        //   of the action. The reason is, that the data in the action response comes from RAM,
        //   and has values up to the nanosecond or so (23/6/2012 15:45:32.424242242), while the
        //   data in the retrieve comes from the DB, which only stores up to the second
        //   (23/6/2012 15:45:32). So, it seems that retrieve-date is earlier than the action
        //   response date, which is impossible. By giving our comparison a 1 second leeway,
        //   this is resolved.

        if (d1 === d2) {
          return 0;
        }
        if (!d1) {
          return -1;
        }
        if (!d2) {
          return +1;
        }
        var delta = d1.getTime() - d2.getTime();
        if (delta < -1000) {
          return -1;
        }
        if (delta > +1000) {
          return +1;
        }
        return 0;
      }

      // MUDO There is a much worse problem: a RAM-created server Date is in the local time of the server
      //      While the time in the DB has no timezone. A RAM-created server Date that is sent over JSON
      //      contains timezone information. A date that is retrieved from the DB does not contain
      //      timezone in RAM, and thus does also not contain timezone information when it is sent over
      //      JSON.
      //      When JavaScript parses a string with timezone information, it takes it into account.
      //      When JavaScript parses a string without timezone information, it assumes the local
      //      timezone.
      //      When server and client are in a different timezone, when the JSON contains timezone information,
      //      the string is interpreted in the timezone of the server. When the JSON does not contain
      //      timezone information, the string is interpreted in the timezone of the client.
      //      For webapplications, this in unacceptable.
      // MUDO for now we work around this by NOT SENDING THE DATA TO THE SERVER IN THE FIRST PLACE
      //      that was another workaround; the effect of this is that we will not see this data after update
      // MUDO but it is obviously wrong

      // MUDO Attempted workaround for PICTOPERFECT-484: send the data to the server

      var InsertAuditableObject = declare([PersistentObject], {
        // InsertAuditableObjects have a `createdAt` and `createdBy` property, which is set by the server.
        // These properties cannot be set in the UI, and are initially null. Once set, the server should always
        // return the same values, except after delete, when they all turn null together again.

        _c_invar: [
          function() {return this._c_prop_string("createdBy");},
          function() {return this._c_prop_date("createdAt");},
          /*
            createdAt must be in the past
            but we cannot test that: server time and time of this local computer are incomparable
           */
          function() {return !!this.get("persistenceId") === !!this.get("createdBy");}, // both exist together or not
          function() {return !!this.get("createdBy") === !!this.get("createdAt");} // both exist together or not
        ],

        // createdBy: String
        createdBy: null,

        // createdAt: Date
        createdAt: null,

        _createdAtSetter: function() {
          // createdAt is read-only
          throw "ERROR createdAt is read-only";
        },

        _createdBySetter: function() {
          // createdBy is read-only
          throw "ERROR createdBy is read-only";
        },

        reload: function(/*Object*/ json) {
          // created.. can change from null to an actual date and username number after create,
          this._c_pre(function() {return json;});
// MUDO PICTOPERFECT-482          this._c_pre(function() {return !!json.persistenceId === !!json.createdBy;});
          this._c_pre(function() {return !!json.createdBy === !!json.createdAt;});
          this._c_pre(function() {return this._c_prop_string(json, "createdBy");});
          /*
          this._c_pre(function() {return !this.get("persistenceId") || !json.persistenceId || (this.get("createdBy") === json.createdBy);});
          The above is true, from the outside, but we cannot test it here, with this contract framework, because persistenceId is set
          by the superclass from null to the new value (on create) before we reach this test. It was true before reload, but not
          halfway. What follows is an equivalent local version.
          */
// MUDO PICTOPERFECT-482          this._c_pre(function() {return !this.get("createdBy") || !json.persistenceId || (this.get("createdBy") === json.createdBy);});

          logger.debug("Trying to convert to date: ", json.createdAt);

          this._c_pre(function() {return this._c_prop_string(json, "createdAt") || this._c_prop_date(json, "createdAt");});
          this._c_pre(function() {return !this.get("createdBy") || !json.persistenceId || compareDate(this.get("createdAt"), stringToDate(json.createdAt)) === 0;});
          if (!this.get("createdBy") || !json.createdBy) {
            this._changeAttrValue("createdBy", json.createdBy);
            this._changeAttrValue("createdAt", stringToDate(json.createdAt));
          }
        },

        // it makes no senses whatsoever to send this data back to the back-end
        // MUDO Workaround for PICTOPERFECT-484: send the data to the server; this does eem to do the trick; is the timezone info above no longer applicable?

        _extendJsonObject:function (/*Object*/ json) {
          json.createdBy = this.get("createdBy");
          json.createdAt = this.get("createdAt");
        },

        _stateToString: function(/*String[]*/ toStrings) {
          toStrings.push("createdAt: " + this.createdAt);
          toStrings.push("createdBy: " + this.createdBy);
        }
      });

      InsertAuditableObject.mid = module.id;
      InsertAuditableObject.stringToDate = stringToDate;
      InsertAuditableObject.compareDate = compareDate;
      return InsertAuditableObject;
    }
);
<|endoftext|>