<commit_before>/**
 * Copyright reelyActive 2020-2022
 * We believe in an open Internet of Things
 */

const Raddec = require('raddec');

/**
 * Device Class
 * Represents a radio-identifiable device.
 */
class Device {

  /**
   * Device constructor
   * @param {String} transmitterId The identifier of the transmitter.
   * @param {Number} transmitterIdType The identifier type of the transmitter.
   * @param {Object} parameters The parameters as a JSON object.
   * @constructor
   */
  constructor(transmitterId, transmitterIdType, parameters) {
    parameters = parameters || {};

    this.transmitterId = transmitterId;
    this.transmitterIdType = transmitterIdType;
    this.raddecs = [];
    this.latestRaddecEvent = null;
    this.pendingEvents = [ Raddec.events.APPEARANCE ];
    this.isPossibleDisplacement = false;
    this.timeout = new Date().getTime() + parameters.delayMilliseconds;
    this.disappearanceTimeout = new Date().getTime() +
                                parameters.disappearanceMilliseconds;
  }

  /**
   * Get the unique device signature based on the ID and type.
   */
  get signature() {
    return this.transmitterId + Raddec.identifiers.SIGNATURE_SEPARATOR +
           this.transmitterIdType;
  }

  /**
   * Determine whether or not the device has an identifier or nearest device
   * which matches one or more of the given signatures.
   */
  hasIdOrNearest(signatures) {
    let self = this;

    if(!signatures || !Array.isArray(signatures)) {
      return false;
    }

    if(signatures.includes(self.signature)) {
      return true;
    }

    if(self.statid && self.statid.hasOwnProperty('deviceIds')) {
      for(const signature of self.statid.deviceIds) {
        if(signatures.includes(signature)) {
          return true;
        }
      }
    }

    let nearest = compileNearest(self.latestRaddecEvent, self.dynamb);

    for(const item of nearest) {
      if(signatures.includes(item.device)) {
        return true;
      }
    }

    return false;
  }

  /**
   * Assemble a standard device representation limited to the given properties,
   * if included.
   * @param {Array} properties The optional subset of properties.
   */
  assemble(properties) {
    let self = this;
    let isPropertySubset = properties && Array.isArray(properties);
    let device = {};

    if(isPropertySubset) {
      properties.forEach(function(property) {
        if((property === 'raddec') && self.latestRaddecEvent)  {
          device.raddec = Object.assign({}, self.latestRaddecEvent);
        }
        else if(self.hasOwnProperty(property)) {
          device[property] = Object.assign({}, self[property]);
        }
      });
    }
    else {
      if(self.latestRaddecEvent) {
        device.raddec = Object.assign({}, self.latestRaddecEvent);
      }
      if(self.dynamb) {
        removeStaleDynambProperties(self);
        if(Object.keys(self.dynamb).length) {
          device.dynamb = Object.assign({}, self.dynamb);
        }
      }
      if(self.statid) {
        device.statid = Object.assign({}, self.statid);
      }
    }

    return device;
  }

  /**
   * Assemble a contextual device representation.
   */
  assembleContext() {
    let self = this;
    let device = {};

    if(self.dynamb) {
      removeStaleDynambProperties(self);
      if(Object.keys(self.dynamb).length) {
        device.dynamb = Object.assign({}, self.dynamb);
      }
    }
    if(self.statid) {
      device.statid = Object.assign({}, self.statid);
    }
    if(self.latestRaddecEvent) {
      device.nearest = compileNearest(self.latestRaddecEvent, self.dynamb);
    }

    return device;
  }

  /**
   * Handle an inbound raddec.
   * @param {Raddec} raddec The inbound raddec.
   * @param {Object} parameters The parameters as a JSON object.
   */
  handleRaddec(raddec, parameters) {
    let self = this;
    parameters = parameters || {};

    determinePossibleDisplacement(self, raddec, parameters);
    determineNewPacket(self, raddec, parameters);
    updateTimeout(self, parameters);
    insertRaddec(self, raddec, parameters);
  }

  /**
   * Handle a dynamb.
   * @param {Object} dynamb The dynamb instance.
   * @param {Object} parameters The parameters as a JSON object.
   */
  handleDynamb(dynamb, parameters) {
    let self = this;
    let hasDynamb = self.hasOwnProperty('dynamb');

    if(!hasDynamb) {
      self.dynamb = {};         // TODO: combine these into a Map
      self.dynambTimeouts = {}; //       for efficient iteration
    }

    for(const property in dynamb) {
      if((property !== 'deviceId') && (property !== 'deviceIdType')) {
        self.dynamb[property] = dynamb[property];
        self.dynambTimeouts[property] = dynamb.timestamp +
                                       parameters.packetCompilationMilliseconds;
      }
    }
  }

  /**
   * Handle a statid.
   * @param {Object} statid The statid instance.
   */
  handleStatid(statid) {
    let self = this;
    let hasStatid = self.hasOwnProperty('statid');

    if(!hasStatid) {
      self.statid = {};
    }

    for(const property in statid) {
      if((property !== 'deviceId') && (property !== 'deviceIdType')) {
        let isNewProperty = !self.statid.hasOwnProperty(property);
        let isArray = Array.isArray(statid[property]);

        if(isNewProperty || !isArray) {
          self.statid[property] = statid[property];
        }
        else {
          statid[property].forEach(function(element) {
            let isNewElement = !self.statid[property].includes(element);

            if(isNewElement) {
              self.statid[property].push(element);
            }
          });
        }
      }
    }
  }

  /**
   * Determine if there are events to handle.
   * @param {Object} parameters The parameters as a JSON object.
   * @param {function} handleEvent The function to call if there's an event.
   */
  determineEvents(parameters, handleEvent) {
    let self = this;
    let currentTime = new Date().getTime();
    let isTimeout = (currentTime > this.timeout);

    removeStaleData(self, parameters);

    if(isTimeout) {
      let raddecEvent = compileRaddecEvent(self, parameters);
      let isEvent = (raddecEvent !== null);

      if(isEvent) {
        let isObservedEvent = raddecEvent.events.some(event =>
                                     parameters.observedEvents.includes(event));
        let isDisappearance = raddecEvent.events.includes(
                                                   Raddec.events.DISAPPEARANCE);
        self.latestRaddecEvent = raddecEvent;
        self.pendingEvents = [];
        self.isPossibleDisplacement = false;
        self.timeout = currentTime + parameters.keepAliveMilliseconds;

        if(isObservedEvent) {
          handleEvent(self.latestRaddecEvent);
        }
        if(isDisappearance) {
          return -1;
        }
      }
    }

    return self.timeout;
  }

}

/**
 * Insert the given raddec, sorting if necessary to maintain ordering of the
 * raddecs array by decreasing timestamp.
 * @param {Device} device The given device instance.
 * @param {Raddec} raddec The inbound raddec.
 * @param {Object} parameters The parameters as a JSON object.
 */
function insertRaddec(device, raddec, parameters) {
  let numberOfRaddecs = device.raddecs.unshift(raddec);

  let isCorrectOrder = (numberOfRaddecs === 1) ||
                       (device.raddecs[1].initialTime <= raddec.initialTime);

  if(!isCorrectOrder) {
    device.raddecs.sort((a, b) => b.initialTime - a.initialTime);
  }
  else {
    device.disappearanceTimeout = raddec.initialTime +
                                  parameters.disappearanceMilliseconds;
  }
}

/**
 * Determine if the given raddec may represent a displacement and update the
 * given device's pendingEvents in consequence.
 * @param {Device} device The given device instance.
 * @param {Raddec} raddec The inbound raddec.
 * @param {Object} parameters The parameters as a JSON object.
 */
function determinePossibleDisplacement(device, raddec, parameters) {
  if(!parameters.observedEvents.includes(Raddec.events.DISPLACEMENT)) {
    return;
  }

  let hasPreviousRaddecEvent = (device.latestRaddecEvent !== null);

  if(hasPreviousRaddecEvent && !device.isPossibleDisplacement) {
    let isDifferentReceiver = (raddec.receiverSignature !==
                               device.latestRaddecEvent.receiverSignature);

    if(isDifferentReceiver) {
      device.isPossibleDisplacement = true;
    }
  }
}

/**
 * Determine if the given raddec includes a new packet and update the given
 * device's pendingEvents in consequence.
 * @param {Device} device The given device instance.
 * @param {Raddec} raddec The inbound raddec.
 * @param {Object} parameters The parameters as a JSON object.
 */
function determineNewPacket(device, raddec, parameters) {
  if(!parameters.observedEvents.includes(Raddec.events.PACKETS)) {
    return;
  }

  let hasPackets = (Array.isArray(raddec.packets) && raddec.packets.length);
  let hasPreviousRaddecEvent = (device.latestRaddecEvent !== null);
  let isPending = device.pendingEvents.includes(Raddec.events.PACKETS);

  if(hasPackets && !isPending && !hasPreviousRaddecEvent) {
    device.pendingEvents.push(Raddec.events.PACKETS);
  }

  else if(hasPackets && !isPending && hasPreviousRaddecEvent) {
    let previousPackets = device.latestRaddecEvent.packets || [];

    for(let cPacket = 0; cPacket < raddec.packets.length; cPacket++) {
      let packet = raddec.packets[cPacket];
      let isNewPacket = !previousPackets.includes(packet);
      if(isNewPacket) {
        return device.pendingEvents.push(Raddec.events.PACKETS);
      }
    }
  }
}

/**
 * Update the timeout if there are pending events.
 * @param {Device} device The given device instance.
 * @param {Object} parameters The parameters as a JSON object.
 */
function updateTimeout(device, parameters) {
  let isPendingEvent = (device.pendingEvents.length > 0) ||
                       device.isPossibleDisplacement;

  if(isPendingEvent) {
    let millisecondsToTimeout = device.timeout - new Date().getTime();
    let isWithinDelay = (millisecondsToTimeout <= parameters.delayMilliseconds);

    if(!isWithinDelay) {
      device.timeout = new Date().getTime() + parameters.delayMilliseconds;
    }
  }
}

/**
 * Remove stale data from the device.
 * @param {Device} device The given device instance.
 * @param {Object} parameters The parameters as a JSON object.
 */
function removeStaleData(device, parameters) {
  let currentTime = new Date().getTime();
  let staleTime = currentTime - parameters.historyMilliseconds;
  let isRemovalComplete = (device.raddecs.length === 0);

  while(!isRemovalComplete) {
    let oldestRaddec = device.raddecs[device.raddecs.length - 1];
    let isStale = (oldestRaddec.initialTime < staleTime);
    if(isStale) {
      device.raddecs.pop();
    }
    isRemovalComplete = (!isStale || (device.raddecs.length === 0));
  }
}

/**
 * Compile the device data into a single raddec based on the given parameters.
 * @param {Device} device The given device instance.
 * @param {Object} parameters The parameters as a JSON object.
 */
function compileRaddec(device, parameters) {
  if(device.raddecs.length === 0) {
    return new Raddec({ transmitterId: device.transmitterId,
                        transmitterIdType: device.transmitterIdType });
  }

  let mostRecentRaddec = device.raddecs[0];
  let decodingCutoffTime = mostRecentRaddec.initialTime -
                           parameters.decodingCompilationMilliseconds;
  let packetCutoffTime = mostRecentRaddec.initialTime -
                         parameters.packetCompilationMilliseconds;
  let compiledRaddec = new Raddec(mostRecentRaddec);

  for(let cRaddec = 1; cRaddec < device.raddecs.length; cRaddec++) {
    let raddec = device.raddecs[cRaddec];
    let isWithinDecodingCutoff = (raddec.initialTime >= decodingCutoffTime);
    let isWithinPacketCutoff = (raddec.initialTime >= packetCutoffTime);

    if(isWithinDecodingCutoff) {
      compiledRaddec.merge(raddec);
    }
    else if(isWithinPacketCutoff) {
      Raddec.mergePackets(raddec.packets, compiledRaddec.packets);
    }
  }

  compiledRaddec.trim();

  return compiledRaddec;
}

/**
 * Compile the device data into a single raddec event.
 * @param {Device} device The given device instance.
 * @param {Object} parameters The parameters as a JSON object.
 */
function compileRaddecEvent(device, parameters) {
  let raddecEvent = compileRaddec(device, parameters);
  let currentTime = new Date().getTime();
  let hasPreviousRaddecEvent = (device.latestRaddecEvent !== null);

  let isDisappearance = (currentTime >= device.disappearanceTimeout);
  let isDisplacement = hasPreviousRaddecEvent &&
                       (raddecEvent.rssiSignature.length > 0) &&
                       (raddecEvent.receiverSignature !==
                        device.latestRaddecEvent.receiverSignature);
  let isKeepAlive = hasPreviousRaddecEvent &&
                    (device.pendingEvents.length === 0) &&
                    (raddecEvent.rssiSignature.length > 0) &&
                    (currentTime >= (device.latestRaddecEvent.initialTime +
                                     parameters.keepAliveMilliseconds));

  if(isDisappearance) {
    device.pendingEvents.push(Raddec.events.DISAPPEARANCE);
  }
  else if(isDisplacement) {
    device.pendingEvents.push(Raddec.events.DISPLACEMENT);
  }
  else if(isKeepAlive)  {
    device.pendingEvents.push(Raddec.events.KEEPALIVE);
  }
  else if(device.pendingEvents.length === 0) {
    return null;
  }

  raddecEvent.events = device.pendingEvents.slice().sort();

  return raddecEvent;
}

/**
 * Compile the nearest devices given the raddec and/or dynamb data.
 * @param {Object} raddec The given raddec.
 * @param {Object} dynamb The given dynamb.
 */
function compileNearest(raddec, dynamb) {
  let nearest = [];

  let hasRssiSignature = raddec && raddec.hasOwnProperty('rssiSignature') &&
                         Array.isArray(raddec.rssiSignature);
  let hasNearest = dynamb && dynamb.hasOwnProperty('nearest') &&
                   Array.isArray(dynamb.nearest);

  if(hasRssiSignature) {
    raddec.rssiSignature.forEach(function(entry) {
      let receiverSignature = entry.receiverId +
                              Raddec.identifiers.SIGNATURE_SEPARATOR +
                              entry.receiverIdType;
      nearest.push({ device: receiverSignature, rssi: entry.rssi });
    });
  }

  if(hasNearest) {
    dynamb.nearest.forEach(function(entry) {
      nearest.push({ device: entry.deviceId, rssi: entry.rssi });
    });
  }

  nearest.sort(function(a, b) {
    return b.rssi - a.rssi;
  });

  return nearest;
}

/**
 * Remove stale dynamb properties from the given device.
 * @param {Device} device The given device instance.
 */
function removeStaleDynambProperties(device) {
  let currentTime = new Date().getTime();

  for(let property in device.dynamb) {
    if(currentTime > device.dynambTimeouts[property]) {
      delete device.dynamb[property];
      delete device.dynambTimeouts[property];
    }
  }
}

module.exports = Device;
<commit_msg>Remove stale dynamb data after disappearanceMilliseconds rather than packetCompilationMilliseconds<commit_after>/**
 * Copyright reelyActive 2020-2022
 * We believe in an open Internet of Things
 */

const Raddec = require('raddec');

/**
 * Device Class
 * Represents a radio-identifiable device.
 */
class Device {

  /**
   * Device constructor
   * @param {String} transmitterId The identifier of the transmitter.
   * @param {Number} transmitterIdType The identifier type of the transmitter.
   * @param {Object} parameters The parameters as a JSON object.
   * @constructor
   */
  constructor(transmitterId, transmitterIdType, parameters) {
    parameters = parameters || {};

    this.transmitterId = transmitterId;
    this.transmitterIdType = transmitterIdType;
    this.raddecs = [];
    this.latestRaddecEvent = null;
    this.pendingEvents = [ Raddec.events.APPEARANCE ];
    this.isPossibleDisplacement = false;
    this.timeout = new Date().getTime() + parameters.delayMilliseconds;
    this.disappearanceTimeout = new Date().getTime() +
                                parameters.disappearanceMilliseconds;
  }

  /**
   * Get the unique device signature based on the ID and type.
   */
  get signature() {
    return this.transmitterId + Raddec.identifiers.SIGNATURE_SEPARATOR +
           this.transmitterIdType;
  }

  /**
   * Determine whether or not the device has an identifier or nearest device
   * which matches one or more of the given signatures.
   */
  hasIdOrNearest(signatures) {
    let self = this;

    if(!signatures || !Array.isArray(signatures)) {
      return false;
    }

    if(signatures.includes(self.signature)) {
      return true;
    }

    if(self.statid && self.statid.hasOwnProperty('deviceIds')) {
      for(const signature of self.statid.deviceIds) {
        if(signatures.includes(signature)) {
          return true;
        }
      }
    }

    let nearest = compileNearest(self.latestRaddecEvent, self.dynamb);

    for(const item of nearest) {
      if(signatures.includes(item.device)) {
        return true;
      }
    }

    return false;
  }

  /**
   * Assemble a standard device representation limited to the given properties,
   * if included.
   * @param {Array} properties The optional subset of properties.
   */
  assemble(properties) {
    let self = this;
    let isPropertySubset = properties && Array.isArray(properties);
    let device = {};

    if(isPropertySubset) {
      properties.forEach(function(property) {
        if((property === 'raddec') && self.latestRaddecEvent)  {
          device.raddec = Object.assign({}, self.latestRaddecEvent);
        }
        else if(self.hasOwnProperty(property)) {
          device[property] = Object.assign({}, self[property]);
        }
      });
    }
    else {
      if(self.latestRaddecEvent) {
        device.raddec = Object.assign({}, self.latestRaddecEvent);
      }
      if(self.dynamb) {
        removeStaleDynambProperties(self);
        if(Object.keys(self.dynamb).length) {
          device.dynamb = Object.assign({}, self.dynamb);
        }
      }
      if(self.statid) {
        device.statid = Object.assign({}, self.statid);
      }
    }

    return device;
  }

  /**
   * Assemble a contextual device representation.
   */
  assembleContext() {
    let self = this;
    let device = {};

    if(self.dynamb) {
      removeStaleDynambProperties(self);
      if(Object.keys(self.dynamb).length) {
        device.dynamb = Object.assign({}, self.dynamb);
      }
    }
    if(self.statid) {
      device.statid = Object.assign({}, self.statid);
    }
    if(self.latestRaddecEvent) {
      device.nearest = compileNearest(self.latestRaddecEvent, self.dynamb);
    }

    return device;
  }

  /**
   * Handle an inbound raddec.
   * @param {Raddec} raddec The inbound raddec.
   * @param {Object} parameters The parameters as a JSON object.
   */
  handleRaddec(raddec, parameters) {
    let self = this;
    parameters = parameters || {};

    determinePossibleDisplacement(self, raddec, parameters);
    determineNewPacket(self, raddec, parameters);
    updateTimeout(self, parameters);
    insertRaddec(self, raddec, parameters);
  }

  /**
   * Handle a dynamb.
   * @param {Object} dynamb The dynamb instance.
   * @param {Object} parameters The parameters as a JSON object.
   */
  handleDynamb(dynamb, parameters) {
    let self = this;
    let hasDynamb = self.hasOwnProperty('dynamb');

    if(!hasDynamb) {
      self.dynamb = {};         // TODO: combine these into a Map
      self.dynambTimeouts = {}; //       for efficient iteration
    }

    for(const property in dynamb) {
      if((property !== 'deviceId') && (property !== 'deviceIdType')) {
        self.dynamb[property] = dynamb[property];
        self.dynambTimeouts[property] = dynamb.timestamp +
                                        parameters.disappearanceMilliseconds;
      }
    }
  }

  /**
   * Handle a statid.
   * @param {Object} statid The statid instance.
   */
  handleStatid(statid) {
    let self = this;
    let hasStatid = self.hasOwnProperty('statid');

    if(!hasStatid) {
      self.statid = {};
    }

    for(const property in statid) {
      if((property !== 'deviceId') && (property !== 'deviceIdType')) {
        let isNewProperty = !self.statid.hasOwnProperty(property);
        let isArray = Array.isArray(statid[property]);

        if(isNewProperty || !isArray) {
          self.statid[property] = statid[property];
        }
        else {
          statid[property].forEach(function(element) {
            let isNewElement = !self.statid[property].includes(element);

            if(isNewElement) {
              self.statid[property].push(element);
            }
          });
        }
      }
    }
  }

  /**
   * Determine if there are events to handle.
   * @param {Object} parameters The parameters as a JSON object.
   * @param {function} handleEvent The function to call if there's an event.
   */
  determineEvents(parameters, handleEvent) {
    let self = this;
    let currentTime = new Date().getTime();
    let isTimeout = (currentTime > this.timeout);

    removeStaleData(self, parameters);

    if(isTimeout) {
      let raddecEvent = compileRaddecEvent(self, parameters);
      let isEvent = (raddecEvent !== null);

      if(isEvent) {
        let isObservedEvent = raddecEvent.events.some(event =>
                                     parameters.observedEvents.includes(event));
        let isDisappearance = raddecEvent.events.includes(
                                                   Raddec.events.DISAPPEARANCE);
        self.latestRaddecEvent = raddecEvent;
        self.pendingEvents = [];
        self.isPossibleDisplacement = false;
        self.timeout = currentTime + parameters.keepAliveMilliseconds;

        if(isObservedEvent) {
          handleEvent(self.latestRaddecEvent);
        }
        if(isDisappearance) {
          return -1;
        }
      }
    }

    return self.timeout;
  }

}

/**
 * Insert the given raddec, sorting if necessary to maintain ordering of the
 * raddecs array by decreasing timestamp.
 * @param {Device} device The given device instance.
 * @param {Raddec} raddec The inbound raddec.
 * @param {Object} parameters The parameters as a JSON object.
 */
function insertRaddec(device, raddec, parameters) {
  let numberOfRaddecs = device.raddecs.unshift(raddec);

  let isCorrectOrder = (numberOfRaddecs === 1) ||
                       (device.raddecs[1].initialTime <= raddec.initialTime);

  if(!isCorrectOrder) {
    device.raddecs.sort((a, b) => b.initialTime - a.initialTime);
  }
  else {
    device.disappearanceTimeout = raddec.initialTime +
                                  parameters.disappearanceMilliseconds;
  }
}

/**
 * Determine if the given raddec may represent a displacement and update the
 * given device's pendingEvents in consequence.
 * @param {Device} device The given device instance.
 * @param {Raddec} raddec The inbound raddec.
 * @param {Object} parameters The parameters as a JSON object.
 */
function determinePossibleDisplacement(device, raddec, parameters) {
  if(!parameters.observedEvents.includes(Raddec.events.DISPLACEMENT)) {
    return;
  }

  let hasPreviousRaddecEvent = (device.latestRaddecEvent !== null);

  if(hasPreviousRaddecEvent && !device.isPossibleDisplacement) {
    let isDifferentReceiver = (raddec.receiverSignature !==
                               device.latestRaddecEvent.receiverSignature);

    if(isDifferentReceiver) {
      device.isPossibleDisplacement = true;
    }
  }
}

/**
 * Determine if the given raddec includes a new packet and update the given
 * device's pendingEvents in consequence.
 * @param {Device} device The given device instance.
 * @param {Raddec} raddec The inbound raddec.
 * @param {Object} parameters The parameters as a JSON object.
 */
function determineNewPacket(device, raddec, parameters) {
  if(!parameters.observedEvents.includes(Raddec.events.PACKETS)) {
    return;
  }

  let hasPackets = (Array.isArray(raddec.packets) && raddec.packets.length);
  let hasPreviousRaddecEvent = (device.latestRaddecEvent !== null);
  let isPending = device.pendingEvents.includes(Raddec.events.PACKETS);

  if(hasPackets && !isPending && !hasPreviousRaddecEvent) {
    device.pendingEvents.push(Raddec.events.PACKETS);
  }

  else if(hasPackets && !isPending && hasPreviousRaddecEvent) {
    let previousPackets = device.latestRaddecEvent.packets || [];

    for(let cPacket = 0; cPacket < raddec.packets.length; cPacket++) {
      let packet = raddec.packets[cPacket];
      let isNewPacket = !previousPackets.includes(packet);
      if(isNewPacket) {
        return device.pendingEvents.push(Raddec.events.PACKETS);
      }
    }
  }
}

/**
 * Update the timeout if there are pending events.
 * @param {Device} device The given device instance.
 * @param {Object} parameters The parameters as a JSON object.
 */
function updateTimeout(device, parameters) {
  let isPendingEvent = (device.pendingEvents.length > 0) ||
                       device.isPossibleDisplacement;

  if(isPendingEvent) {
    let millisecondsToTimeout = device.timeout - new Date().getTime();
    let isWithinDelay = (millisecondsToTimeout <= parameters.delayMilliseconds);

    if(!isWithinDelay) {
      device.timeout = new Date().getTime() + parameters.delayMilliseconds;
    }
  }
}

/**
 * Remove stale data from the device.
 * @param {Device} device The given device instance.
 * @param {Object} parameters The parameters as a JSON object.
 */
function removeStaleData(device, parameters) {
  let currentTime = new Date().getTime();
  let staleTime = currentTime - parameters.historyMilliseconds;
  let isRemovalComplete = (device.raddecs.length === 0);

  while(!isRemovalComplete) {
    let oldestRaddec = device.raddecs[device.raddecs.length - 1];
    let isStale = (oldestRaddec.initialTime < staleTime);
    if(isStale) {
      device.raddecs.pop();
    }
    isRemovalComplete = (!isStale || (device.raddecs.length === 0));
  }
}

/**
 * Compile the device data into a single raddec based on the given parameters.
 * @param {Device} device The given device instance.
 * @param {Object} parameters The parameters as a JSON object.
 */
function compileRaddec(device, parameters) {
  if(device.raddecs.length === 0) {
    return new Raddec({ transmitterId: device.transmitterId,
                        transmitterIdType: device.transmitterIdType });
  }

  let mostRecentRaddec = device.raddecs[0];
  let decodingCutoffTime = mostRecentRaddec.initialTime -
                           parameters.decodingCompilationMilliseconds;
  let packetCutoffTime = mostRecentRaddec.initialTime -
                         parameters.packetCompilationMilliseconds;
  let compiledRaddec = new Raddec(mostRecentRaddec);

  for(let cRaddec = 1; cRaddec < device.raddecs.length; cRaddec++) {
    let raddec = device.raddecs[cRaddec];
    let isWithinDecodingCutoff = (raddec.initialTime >= decodingCutoffTime);
    let isWithinPacketCutoff = (raddec.initialTime >= packetCutoffTime);

    if(isWithinDecodingCutoff) {
      compiledRaddec.merge(raddec);
    }
    else if(isWithinPacketCutoff) {
      Raddec.mergePackets(raddec.packets, compiledRaddec.packets);
    }
  }

  compiledRaddec.trim();

  return compiledRaddec;
}

/**
 * Compile the device data into a single raddec event.
 * @param {Device} device The given device instance.
 * @param {Object} parameters The parameters as a JSON object.
 */
function compileRaddecEvent(device, parameters) {
  let raddecEvent = compileRaddec(device, parameters);
  let currentTime = new Date().getTime();
  let hasPreviousRaddecEvent = (device.latestRaddecEvent !== null);

  let isDisappearance = (currentTime >= device.disappearanceTimeout);
  let isDisplacement = hasPreviousRaddecEvent &&
                       (raddecEvent.rssiSignature.length > 0) &&
                       (raddecEvent.receiverSignature !==
                        device.latestRaddecEvent.receiverSignature);
  let isKeepAlive = hasPreviousRaddecEvent &&
                    (device.pendingEvents.length === 0) &&
                    (raddecEvent.rssiSignature.length > 0) &&
                    (currentTime >= (device.latestRaddecEvent.initialTime +
                                     parameters.keepAliveMilliseconds));

  if(isDisappearance) {
    device.pendingEvents.push(Raddec.events.DISAPPEARANCE);
  }
  else if(isDisplacement) {
    device.pendingEvents.push(Raddec.events.DISPLACEMENT);
  }
  else if(isKeepAlive)  {
    device.pendingEvents.push(Raddec.events.KEEPALIVE);
  }
  else if(device.pendingEvents.length === 0) {
    return null;
  }

  raddecEvent.events = device.pendingEvents.slice().sort();

  return raddecEvent;
}

/**
 * Compile the nearest devices given the raddec and/or dynamb data.
 * @param {Object} raddec The given raddec.
 * @param {Object} dynamb The given dynamb.
 */
function compileNearest(raddec, dynamb) {
  let nearest = [];

  let hasRssiSignature = raddec && raddec.hasOwnProperty('rssiSignature') &&
                         Array.isArray(raddec.rssiSignature);
  let hasNearest = dynamb && dynamb.hasOwnProperty('nearest') &&
                   Array.isArray(dynamb.nearest);

  if(hasRssiSignature) {
    raddec.rssiSignature.forEach(function(entry) {
      let receiverSignature = entry.receiverId +
                              Raddec.identifiers.SIGNATURE_SEPARATOR +
                              entry.receiverIdType;
      nearest.push({ device: receiverSignature, rssi: entry.rssi });
    });
  }

  if(hasNearest) {
    dynamb.nearest.forEach(function(entry) {
      nearest.push({ device: entry.deviceId, rssi: entry.rssi });
    });
  }

  nearest.sort(function(a, b) {
    return b.rssi - a.rssi;
  });

  return nearest;
}

/**
 * Remove stale dynamb properties from the given device.
 * @param {Device} device The given device instance.
 */
function removeStaleDynambProperties(device) {
  let currentTime = new Date().getTime();

  for(let property in device.dynamb) {
    if(currentTime > device.dynambTimeouts[property]) {
      delete device.dynamb[property];
      delete device.dynambTimeouts[property];
    }
  }
}

module.exports = Device;
<|endoftext|>


<commit_before>#!/bin/env node

'use strict';

const program = require('commander');
const path = require('path');
const dbconfig = require('../src/util/dbconfig');
const imp = require('../src/import/import');
const fs = require('fs-extra');
const exec = require('child_process').exec;
const config = require('../src/util/config');
const log = require('../src/couch/log');

program
    .usage('<file> <config>')
    .option('-l, --limit <number>', 'Limit of files to import')
    .parse(process.argv);

let prom = Promise.resolve();
if (program.args[0] && program.args[1]) {
    const config = dbconfig.import(program.args[1]);
    const file = path.resolve(program.args[0]);

    prom = prom.then(() => {
        return imp.import(config, file);
    });
} else if(!program.args[0] && !program.args[1]) {
    // import all
    let homeDir = config.get('homeDir');
    if (!homeDir) {
        console.error('homeDir must be set to import all');
        process.exit(1);
    }

    prom = prom.then(() => findFiles(homeDir))
        .then(paths => {
            let p = Promise.resolve();
            const limit = +program.limit || paths.length;
            for(let i=0; i<limit ; i++) {
                let filepath = path.join(homeDir, paths[i].dir, paths[i].base);
                p = p.then(() => {
                    let config = dbconfig.import(path.join(paths[i].dir, 'config.js'));
                    return imp.import(config, filepath);
                }).then(() => {
                    // mv to processed
                    return new Promise(function(resolve, reject) {
                        fs.rename(filepath, path.join(homeDir, paths[i].dir, 'processed', paths[i].base), function(err) {
                            if(err) return reject(err);
                            resolve();
                        });
                    });
                }).catch(() => {
                    // mv to errored
                    return new Promise(function(resolve, reject) {
                        fs.rename(filepath, path.join(homeDir, paths[i].dir, 'errored', paths[i].base), function(err) {
                            if(err) return reject(err);
                            resolve();
                        });
                    });
                });
            }
            return p;
        });
} else {
    console.error('Import command should be called either with 2 arguments or with none');
    process.exit(1);
}

prom.catch(function (err) {
    console.error(err.message || err);
    process.exit(1);
});

function findFiles(homeDir) {
    return new Promise(function (resolve, reject) {
        exec('find . -maxdepth 3 -mindepth 3 -type f', {cwd: homeDir, maxBuffer: 10*1000*1024}, function (err, stdout) {
            console.log(stdout);
            let dirs = new Map();
            if(err) return reject(err);
            let paths = stdout.split('\n');
            paths = paths.filter(path => path);
            let parsedPaths = paths.map(path.parse);
            parsedPaths = parsedPaths.filter(parsedPath => parsedPath.base !== 'config.js');
            parsedPaths.forEach(parsedPath => {
                dirs.set(parsedPath.dir, true);
            });

            dirs.forEach((val, key) => {
                fs.mkdirpSync(path.join(homeDir, key, 'processed'));
                fs.mkdirpSync(path.join(homeDir, key, 'errored'));
            });
            resolve(parsedPaths);
        });
    });
}
<commit_msg>Smarter find<commit_after>#!/bin/env node

'use strict';

const program = require('commander');
const path = require('path');
const dbconfig = require('../src/util/dbconfig');
const imp = require('../src/import/import');
const fs = require('fs-extra');
const exec = require('child_process').exec;
const config = require('../src/util/config');
const log = require('../src/couch/log');

program
    .usage('<file> <config>')
    .option('-l, --limit <number>', 'Limit of files to import')
    .parse(process.argv);

let prom = Promise.resolve();
if (program.args[0] && program.args[1]) {
    const config = dbconfig.import(program.args[1]);
    const file = path.resolve(program.args[0]);

    prom = prom.then(() => {
        return imp.import(config, file);
    });
} else if(!program.args[0] && !program.args[1]) {
    // import all
    let homeDir = config.get('homeDir');
    if (!homeDir) {
        console.error('homeDir must be set to import all');
        process.exit(1);
    }

    prom = prom.then(() => findFiles(homeDir))
        .then(paths => {
            let p = Promise.resolve();
            const limit = +program.limit || paths.length;
            for(let i=0; i<limit ; i++) {
                let filepath = path.join(homeDir, paths[i].dir, paths[i].base);
                p = p.then(() => {
                    let config = dbconfig.import(path.join(paths[i].dir, 'import.js'));
                    return imp.import(config, filepath);
                }).then(() => {
                    // mv to processed
                    return new Promise(function(resolve, reject) {
                        fs.rename(filepath, path.join(homeDir, paths[i].dir, 'processed', paths[i].base), function(err) {
                            if(err) return reject(err);
                            resolve();
                        });
                    });
                }).catch(() => {
                    // mv to errored
                    return new Promise(function(resolve, reject) {
                        fs.rename(filepath, path.join(homeDir, paths[i].dir, 'errored', paths[i].base), function(err) {
                            if(err) return reject(err);
                            resolve();
                        });
                    });
                });
            }
            return p;
        });
} else {
    console.error('Import command should be called either with 2 arguments or with none');
    process.exit(1);
}

prom.catch(function (err) {
    console.error(err.message || err);
    process.exit(1);
});

function findFiles(homeDir) {
    return new Promise(function (resolve, reject) {
        exec('find . -not -path \'*/\.*\' -maxdepth 3 -mindepth 3 -type f', {cwd: homeDir, maxBuffer: 10*1000*1024}, function (err, stdout) {
            let dirs = new Map();
            if(err) return reject(err);
            let paths = stdout.split('\n');
            paths = paths.filter(path => path);
            let parsedPaths = paths.map(path.parse);
            parsedPaths = parsedPaths.filter(parsedPath => parsedPath.base !== 'import.js');
            parsedPaths.forEach(parsedPath => {
                dirs.set(parsedPath.dir, true);
            });

            dirs.forEach((val, key) => {
                fs.mkdirpSync(path.join(homeDir, key, 'processed'));
                fs.mkdirpSync(path.join(homeDir, key, 'errored'));
            });
            resolve(parsedPaths);
        });
    });
}
<|endoftext|>


<commit_before>module.exports = function(gulp, $) {

  gulp.task('run:flow', function(done) {

    var runTasks = [
      'jshint',
      'clean:dist',
    ];

    if( $.is.release ) {

      // dist flow

      // TODO: review and redefine [ build flow ]
      // runTasks = runTasks.concat([ 'clean:dist:unused-files' ]);

      if( $.is.preview ) {
        runTasks = runTasks.concat([ 'webserver:preview' ]);
      }

    } else {

      // dev flow
      // runTasks = runTasks.concat([ 'watch' ]); // TODO: review and redefine

    }

    runTasks = runTasks.concat([ done ]);
    $.runSequence.apply(null, runTasks);

  });

  //---

  gulp.task('default', ['run:flow'], function() {

    $.projectInfoMsg();

  });

};
<commit_msg>#35 default tasks calls watch in dev flow<commit_after>module.exports = function(gulp, $) {

  gulp.task('run:flow', function(done) {

    var runTasks = [
      'jshint',
      'clean:dist',
    ];

    if( $.is.release ) {

      // dist flow

      // TODO: review and redefine [ build flow ]
      // runTasks = runTasks.concat([ 'clean:dist:unused-files' ]);

      if( $.is.preview ) {
        runTasks = runTasks.concat([ 'webserver:preview' ]);
      }

    } else {

      // dev flow
      runTasks = runTasks.concat([ 'watch' ]);

    }

    runTasks = runTasks.concat([ done ]);
    $.runSequence.apply(null, runTasks);

  });

  //---

  gulp.task('default', ['run:flow'], function() {

    $.projectInfoMsg();

  });

};
<|endoftext|>


<commit_before>import React, { Component, cloneElement } from "react"
import PropTypes from "prop-types"
import uniq from "lodash/uniq"
import Form from "../Form"
import Validator from "../Validator"
import Navigator from "../Navigator"

export default class EnhancedForm extends Component {
  static propTypes = {
    fields: PropTypes.arrayOf(
      PropTypes.shape({
        tab: PropTypes.string,
        label: PropTypes.node,
        path: PropTypes.string.isRequired,
        input: PropTypes.oneOfType([PropTypes.element, PropTypes.func])
          .isRequired,
        hidden: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
        size: PropTypes.number
      })
    ).isRequired,
    initialValues: PropTypes.object,
    validations: PropTypes.object,
    onSubmit: PropTypes.func,
    children: PropTypes.func.isRequired
  }

  getForm = () => this.form

  getFields = () =>
    this.props.fields.filter(
      field =>
        !(typeof field.hidden === "function"
          ? field.hidden(this.parseForm(this.form))
          : field.hidden)
    )

  getTabs = () => {
    const fields = this.getFields()
    if (!fields.find(field => field.tab)) {
      return [""]
    }
    return uniq(fields.filter(field => field.tab).map(field => field.tab))
  }

  getFieldsByTab = tab => {
    return this.getFields().filter(field => {
      return !field.tab || field.tab === tab
    })
  }

  getErrorsByTab = (errors, tab) => {
    if (!errors) {
      return null
    }
    const fields = this.getFieldsByTab(tab)
    const fieldsWithError = fields.filter(field => errors[field.path])
    if (!fieldsWithError.length) {
      return null
    }
    return fieldsWithError
  }

  parseForm(form) {
    if (!form) {
      return {}
    }
    return {
      isPristine: form.isPristine,
      isDirty: form.isDirty,
      isSubmitted: form.isSubmitted,
      getValue: form.getValue,
      setValue: form.setValue,
      setPristine: form.setPristine,
      setDirty: form.setDirty,
      submit: form.handleSubmit,
      reset: form.reset
    }
  }

  submit = () => this.form.handleSubmit()

  handleSubmit = values => {
    this.getFields().forEach(field => this.form.setDirty(field.path))
    const errors = this.validator.getErrors()
    if (errors) {
      return
    }
    if (this.props.onSubmit) {
      this.props.onSubmit({ ...this.parseForm(this.form), values })
    }
  }

  renderInput = (form, field, errors) => {
    const value = form.getValue(field.path)
    const setValue = event =>
      form.setValue(
        field.path,
        event && event.target ? event.target.value : event
      )
    if (typeof field.input === "function") {
      return field.input({
        form: this.parseForm(form),
        field,
        value,
        setValue,
        errors
      })
    }
    return cloneElement(field.input, { value, onChange: setValue })
  }

  render() {
    const tabs = this.getTabs()
    return (
      <Form
        ref={form => (this.form = form)}
        initialValues={this.props.initialValues}
        noValidate={true}
        onSubmit={this.handleSubmit}
      >
        {form => (
          <Validator
            ref={validator => (this.validator = validator)}
            validations={this.props.validations}
            values={form.values}
          >
            {validator => (
              <Navigator initialScene={tabs[0]}>
                {navigator =>
                  tabs.reduce(
                    (result, tab) => ({
                      ...result,
                      [tab]: this.props.children({
                        ...this.parseForm(form),
                        tabs: tabs[0] !== "" &&
                          tabs.map(tab => ({
                            label: tab,
                            active: tab === navigator.currentScene,
                            errors: this.getErrorsByTab(validator.errors, tab),
                            onClick: () => navigator.setScene(tab)
                          })),
                        fields: this.getFieldsByTab(
                          navigator.currentScene
                        ).map(field => ({
                          ...field,
                          input: this.renderInput(
                            form,
                            field,
                            validator.errors
                          ),
                          error: validator.errors &&
                            validator.errors[field.path]
                        })),
                        values: form.values,
                        errors: validator.errors
                      })
                    }),
                    {}
                  )}
              </Navigator>
            )}
          </Validator>
        )}
      </Form>
    )
  }
}
<commit_msg>Fix EnhancedForm (again)<commit_after>import React, { Component, cloneElement } from "react"
import PropTypes from "prop-types"
import uniq from "lodash/uniq"
import Form from "../Form"
import Validator from "../Validator"
import Navigator from "../Navigator"

export default class EnhancedForm extends Component {
  static propTypes = {
    fields: PropTypes.arrayOf(
      PropTypes.shape({
        tab: PropTypes.string,
        label: PropTypes.node,
        path: PropTypes.string.isRequired,
        input: PropTypes.oneOfType([PropTypes.element, PropTypes.func])
          .isRequired,
        hidden: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
        size: PropTypes.number
      })
    ).isRequired,
    initialValues: PropTypes.object,
    validations: PropTypes.object,
    onSubmit: PropTypes.func,
    children: PropTypes.func.isRequired
  }

  getForm = () => this.form

  getFields = () =>
    this.form
      ? this.props.fields.filter(
          field =>
            !(typeof field.hidden === "function"
              ? field.hidden(this.parseForm(this.form))
              : field.hidden)
        )
      : this.props.fields

  getTabs = () => {
    const fields = this.getFields()
    if (!fields.find(field => field.tab)) {
      return [""]
    }
    return uniq(fields.filter(field => field.tab).map(field => field.tab))
  }

  getFieldsByTab = tab => {
    return this.getFields().filter(field => {
      return !field.tab || field.tab === tab
    })
  }

  getErrorsByTab = (errors, tab) => {
    if (!errors) {
      return null
    }
    const fields = this.getFieldsByTab(tab)
    const fieldsWithError = fields.filter(field => errors[field.path])
    if (!fieldsWithError.length) {
      return null
    }
    return fieldsWithError
  }

  parseForm(form) {
    if (!form) {
      return {}
    }
    return {
      isPristine: form.isPristine,
      isDirty: form.isDirty,
      isSubmitted: form.isSubmitted,
      getValue: form.getValue,
      setValue: form.setValue,
      setPristine: form.setPristine,
      setDirty: form.setDirty,
      submit: form.handleSubmit,
      reset: form.reset
    }
  }

  submit = () => this.form.handleSubmit()

  handleSubmit = values => {
    this.getFields().forEach(field => this.form.setDirty(field.path))
    const errors = this.validator.getErrors()
    if (errors) {
      return
    }
    if (this.props.onSubmit) {
      this.props.onSubmit({ ...this.parseForm(this.form), values })
    }
  }

  renderInput = (form, field, errors) => {
    const value = form.getValue(field.path)
    const setValue = event =>
      form.setValue(
        field.path,
        event && event.target ? event.target.value : event
      )
    if (typeof field.input === "function") {
      return field.input({
        form: this.parseForm(form),
        field,
        value,
        setValue,
        errors
      })
    }
    return cloneElement(field.input, { value, onChange: setValue })
  }

  render() {
    const tabs = this.getTabs()
    return (
      <Form
        ref={form => (this.form = form)}
        initialValues={this.props.initialValues}
        noValidate={true}
        onSubmit={this.handleSubmit}
      >
        {form => (
          <Validator
            ref={validator => (this.validator = validator)}
            validations={this.props.validations}
            values={form.values}
          >
            {validator => (
              <Navigator initialScene={tabs[0]}>
                {navigator =>
                  tabs.reduce(
                    (result, tab) => ({
                      ...result,
                      [tab]: this.props.children({
                        ...this.parseForm(form),
                        tabs: tabs[0] !== "" &&
                          tabs.map(tab => ({
                            label: tab,
                            active: tab === navigator.currentScene,
                            errors: this.getErrorsByTab(validator.errors, tab),
                            onClick: () => navigator.setScene(tab)
                          })),
                        fields: this.getFieldsByTab(
                          navigator.currentScene
                        ).map(field => ({
                          ...field,
                          input: this.renderInput(
                            form,
                            field,
                            validator.errors
                          ),
                          error: validator.errors &&
                            validator.errors[field.path]
                        })),
                        values: form.values,
                        errors: validator.errors
                      })
                    }),
                    {}
                  )}
              </Navigator>
            )}
          </Validator>
        )}
      </Form>
    )
  }
}
<|endoftext|>


<commit_before>import React from 'react';
import { Button } from 'react-bootstrap';
import Select from 'react-select';
import Promise from 'bluebird';
import { EntityDataModelApi, LinkingApi } from 'loom-data';
import DefineLinkedEntityType from './DefineLinkedEntityType';
import DefineLinkedEntitySet from './DefineLinkedEntitySet';
import Page from '../../../components/page/Page';
import buttonStyles from '../../../core/styles/buttons.css';
import styles from './styles.module.css';

export class Link extends React.Component {

  constructor() {
    super();
    this.state = {
      allEntitySets: [],
      selectedEntitySetIds: [],
      availablePropertyTypes: {},
      links: [],
      entityTypeIdToEntitySet: {},
      propertyTypeIdToEntityType: {},
      newRow: false,
      editingPropertyType: '',
      editingEntitySets: [],
      needsOneEntitySetError: false,
      loadEntitySetsError: false,
      linkingError: false,
      linkingSuccess: false,
      chooseLinks: false,
      chooseLinkedEntityType: false,
      entityTypeCreated: false,
      linkingEntityTypeId: ''
    };
  }

  componentDidMount() {
    this.loadAllEntitySets();
  }

  loadAllEntitySets = () => {
    EntityDataModelApi.getAllEntitySets()
    .then((allEntitySets) => {
      const entityTypeIdToEntitySet = {};
      allEntitySets.forEach((entitySet) => {
        if (entityTypeIdToEntitySet[entitySet.entityTypeId]) {
          entityTypeIdToEntitySet[entitySet.entityTypeId].push(entitySet);
        }
        else {
          entityTypeIdToEntitySet[entitySet.entityTypeId] = [entitySet];
        }
      });
      this.setState({ allEntitySets, entityTypeIdToEntitySet });
    }).catch(() => {
      this.setState({ loadEntitySetsError: true });
    });
  }

  renderExistingLinks = () => {
    return this.state.links.map((link) => {
      let entitySetsString = '';
      if (link.entitySets.length >= 1) {
        entitySetsString = link.entitySets[0].name;
      }
      if (link.entitySets.length > 1) {
        for (let i = 1; i < link.entitySets.length; i += 1) {
          entitySetsString = entitySetsString.concat(', ').concat(link.entitySets[i].name);
        }
      }
      return (

            <button
                className={buttonStyles.deleteButton}
                onClick={() => {
                  this.removeLink(link.propertyType.id);
                }}>-</button>

          <td className={`${styles.propertyTypeSelect} ${styles.linkBox}`}>{link.propertyType.title}
          <td className={`${styles.entitySetsSelect} ${styles.linkBox}`}>{entitySetsString}

      );
    });
  }

  addRow = () => {
    this.setState({ newRow: true });
  }

  removeLink = (propertyTypeId) => {
    const links = this.state.links.filter((link) => {
      return link.propertyType.id !== propertyTypeId;
    });
    this.setState({ links });
  }

  renderAddRowButton = () => {
    if (this.state.newRow) return null;
    return (

          <button className={buttonStyles.addButton} onClick={this.addRow}>+</button>

    );
  }

  onPropertyTypeLinkChange = (e) => {
    const editingPropertyType = (e && e.value) ? e.value : '';
    this.setState({
      editingPropertyType,
      editingEntitySets: [],
      needsOneEntitySetError: false
    });
  }

  onEntitySetsLinkChange = (options) => {
    const editingEntitySets = options.map((option) => {
      return option.value;
    });
    this.setState({ editingEntitySets });
  }

  getPropertyTypeOptions() {
    const availablePropertyTypes = Object.assign({}, this.state.availablePropertyTypes);
    this.state.links.forEach((link) => {
      delete availablePropertyTypes[link.propertyType.id];
    });
    return Object.values(availablePropertyTypes).map((propertyType) => {
      return { label: propertyType.title, value: propertyType.id };
    });
  }

  getEntitySetsOptions = () => {
    const entitySetOptions = [];
    if (this.state.editingPropertyType && this.state.editingPropertyType.length > 0) {
      const entityTypes = this.state.propertyTypeIdToEntityType[this.state.editingPropertyType];
      entityTypes.forEach((entityType) => {
        const entitySets = this.state.entityTypeIdToEntitySet[entityType.id];
        entitySets.forEach((entitySet) => {
          if (this.state.selectedEntitySetIds.includes(entitySet.id)) {
            entitySetOptions.push({ label: entitySet.name, value: entitySet.id });
          }
        });
      });
    }
    return entitySetOptions;
  }

  saveLink = () => {
    if (this.state.editingEntitySets.length < 1) {
      this.setState({ needsOneEntitySetError: true });
      return;
    }
    const propertyType = this.state.availablePropertyTypes[this.state.editingPropertyType];
    const entitySets = this.state.allEntitySets.filter((entitySet) => {
      return this.state.editingEntitySets.includes(entitySet.id);
    });
    const links = this.state.links;
    links.push({ propertyType, entitySets });
    this.setState({
      links,
      newRow: false,
      editingPropertyType: '',
      editingEntitySets: [],
      needsOneEntitySetError: false
    });
  }

  renderNewLink = () => {
    if (this.state.newRow) {
      return (

            <Select
                className={styles.propertyTypeSelect}
                options={this.getPropertyTypeOptions()}
                value={this.state.editingPropertyType}
                onChange={this.onPropertyTypeLinkChange} />

            <Select
                className={styles.entitySetsSelect}
                options={this.getEntitySetsOptions()}
                value={this.state.editingEntitySets}
                onChange={this.onEntitySetsLinkChange}
                multi />

            <Button bsStyle="info" onClick={this.saveLink} className={styles.spacerLeft}>Add links</Button>

      );
    }
    return null;
  }

  renderNotEnoughEntitySetsError = () => {
    if (this.state.needsOneEntitySetError) {
      return
You must specify an entity set to link on.
;
    }
    return null;
  }

  chooseLinks = () => {
    this.setState({
      chooseLinks: true,
      newRow: true
    });
  }

  renderChooseLinksButton = () => {
    if (this.state.selectedEntitySetIds.length < 1 || this.state.chooseLinks) return null;
    return (

        <Button
            bsStyle="primary"
            onClick={this.chooseLinks}
            className={styles.createEntityTypeButton}>
          {'Define the links to join the entity sets'}</Button>

    );
  }

  renderChooseLinks = () => {
    if (!this.state.chooseLinks) return null;
    return (

Step 2. Choose property types to link.

              Property Type
              Entity Sets

            {this.renderExistingLinks()}
            {this.renderNewLink()}
            {this.renderAddRowButton()}

        {this.renderNotEnoughEntitySetsError()}

    );
  }

  loadPropertyTypes = (entityTypes) => {
    const propertyTypeIdToEntityType = {};
    const propertyTypeIds = new Set();
    entityTypes.forEach((entityType) => {
      entityType.properties.forEach((propertyTypeId) => {
        propertyTypeIds.add(propertyTypeId);
        if (propertyTypeIdToEntityType[propertyTypeId]) {
          propertyTypeIdToEntityType[propertyTypeId].push(entityType);
        }
        else {
          propertyTypeIdToEntityType[propertyTypeId] = [entityType];
        }
      });
    });
    Promise.map(propertyTypeIds, (propertyTypeId) => {
      return EntityDataModelApi.getPropertyType(propertyTypeId);
    }).then((propertyTypes) => {
      const availablePropertyTypes = {};
      propertyTypes.forEach((propertyType) => {
        availablePropertyTypes[propertyType.id] = propertyType;
      });
      this.setState({
        availablePropertyTypes,
        propertyTypeIdToEntityType,
        editingPropertyType: '',
        editingEntitySets: [],
        links: [],
        newRow: true,
        needsOneEntitySetError: false,
        loadEntitySetsError: false
      });
    }).catch(() => {
      this.setState({ loadEntitySetsError: true });
    });
  }

  loadPropertyTypesForSelectedEntitySets = (selectedEntitySetIds) => {
    const entityTypeIds = new Set();
    selectedEntitySetIds.forEach((entitySetId) => {
      const entitySet = this.state.allEntitySets.filter((entitySetObj) => {
        return entitySetObj.id === entitySetId;
      })[0];
      entityTypeIds.add(entitySet.entityTypeId);
    });
    Promise.map(entityTypeIds, (entityTypeId) => {
      return EntityDataModelApi.getEntityType(entityTypeId);
    }).then((entityTypes) => {
      this.loadPropertyTypes(entityTypes);
    }).catch(() => {
      this.setState({ loadEntitySetsError: true });
    });
  }

  onSelectedEntitySetChange = (options) => {
    const selectedEntitySetIds = options.map((option) => {
      return option.value;
    });
    const chooseLinks = (selectedEntitySetIds.length < 1) ? false : this.state.chooseLinks;
    this.setState({
      selectedEntitySetIds,
      chooseLinks,
      chooseLinkedEntityType: false
    });
    this.loadPropertyTypesForSelectedEntitySets(selectedEntitySetIds);
  }

  renderLoadEntitySetsError = () => {
    if (this.state.loadEntitySetsError) {
      return (
Unable to load entity sets.
);
    }
    return null;
  }

  renderChooseEntitySets = () => {
    const entitySetOptions = this.state.allEntitySets.map((entitySet) => {
      return ({ label: entitySet.name, value: entitySet.id });
    });
    return (

Step 1. Choose entity sets to link.

        <Select
            value={this.state.selectedEntitySetIds}
            options={entitySetOptions}
            multi
            onChange={this.onSelectedEntitySetChange} />
        {this.renderLoadEntitySetsError()}

    );
  }

  renderDefineLinkedEntityTypeButton = () => {
    if (this.state.selectedEntitySetIds.length > 0
      && this.state.links.length >= 1
      && !this.state.chooseLinkedEntityType) {
      return (

          <Button
              bsStyle="primary"
              onClick={this.chooseLinkedEntityType}
              className={styles.createEntityTypeButton}>
            {'Define the linked entity type to create'}</Button>

      );
    }
    return null;
  }

  renderDefineLinkedEntityType = () => {
    if (this.state.chooseLinkedEntityType) {
      return (
        <DefineLinkedEntityType
            availablePropertyTypes={this.state.availablePropertyTypes}
            linkFn={this.createLinkingEntityType} />
      );
    }
    return null;
  }

  chooseLinkedEntityType = () => {
    this.setState({ chooseLinkedEntityType: true });
  }

  performLink = (name, title, description) => {
    const entitySet = {
      name,
      title,
      description,
      entityTypeId: this.state.linkingEntityTypeId
    };
    const linkingProperties = this.state.links.map((link) => {
      const propertyMap = {};
      link.entitySets.forEach((linkEntitySet) => {
        propertyMap[linkEntitySet.id] = link.propertyType.id;
      });
      return propertyMap;
    });
    const linkingEntitySet = { entitySet, linkingProperties };
    LinkingApi.linkEntitySets(linkingEntitySet)
    .then(() => {
      this.setState({
        linkingSuccess: true,
        linkingError: false
      });
    }).catch(() => {
      this.setState({
        linkingError: true,
        linkingSuccess: false
      });
    });
  }

  createLinkingEntityType = (entityType, deidentified) => {
    const entityTypeIds = new Set();
    this.state.allEntitySets.forEach((entitySet) => {
      if (this.state.selectedEntitySetIds.includes(entitySet.id)) {
        entityTypeIds.add(entitySet.entityTypeId);
      }
    });
    LinkingApi.createLinkingEntityType({ entityType, entityTypeIds, deidentified })
    .then((linkingEntityTypeId) => {
      if (linkingEntityTypeId) {
        this.setState({
          entityTypeCreated: true,
          deidentified,
          linkingEntityTypeId
        });
      }
      else {
        this.setState({
          entityTypeCreated: false,
          linkingError: true,
          linkingSuccess: false
        });
      }
    }).catch(() => {
      this.setState({
        entityTypeCreated: false,
        linkingError: true,
        linkingSuccess: false
      });
    });
  }

  renderLinkingStatus = () => {
    if (this.state.linkingSuccess) {
      return
Success! Your linked entity set is being created.
;
    }
    else if (this.state.linkingError) {
      return
Unable to link entity sets.
;
    }
    return null;
  }

  render() {
    let content;
    if (this.state.linkingSuccess) {
      content =
Success! Your linked entity set is being created.
;
    }
    else if (this.state.entityTypeCreated) {
      content = <DefineLinkedEntitySet linkFn={this.performLink} />;
    }
    else {
      content = (

          {this.renderChooseEntitySets()}
          {this.renderChooseLinksButton()}
          {this.renderChooseLinks()}
          {this.renderDefineLinkedEntityTypeButton()}
          {this.renderDefineLinkedEntityType()}
          {this.renderLinkingStatus()}

      );
    }
    return (
      <Page>
        <Page.Header>
          <Page.Title>Link</Page.Title>
        </Page.Header>
        <Page.Body>{content}</Page.Body>
      </Page>
    );
  }
}

export default Link;
<commit_msg>filter out null entity sets for link<commit_after>import React from 'react';
import { Button } from 'react-bootstrap';
import Select from 'react-select';
import Promise from 'bluebird';
import { EntityDataModelApi, LinkingApi } from 'loom-data';
import DefineLinkedEntityType from './DefineLinkedEntityType';
import DefineLinkedEntitySet from './DefineLinkedEntitySet';
import Page from '../../../components/page/Page';
import buttonStyles from '../../../core/styles/buttons.css';
import styles from './styles.module.css';

export class Link extends React.Component {

  constructor() {
    super();
    this.state = {
      allEntitySets: [],
      selectedEntitySetIds: [],
      availablePropertyTypes: {},
      links: [],
      entityTypeIdToEntitySet: {},
      propertyTypeIdToEntityType: {},
      newRow: false,
      editingPropertyType: '',
      editingEntitySets: [],
      needsOneEntitySetError: false,
      loadEntitySetsError: false,
      linkingError: false,
      linkingSuccess: false,
      chooseLinks: false,
      chooseLinkedEntityType: false,
      entityTypeCreated: false,
      linkingEntityTypeId: ''
    };
  }

  componentDidMount() {
    this.loadAllEntitySets();
  }

  loadAllEntitySets = () => {
    EntityDataModelApi.getAllEntitySets()
    .then((allEntitySetsRaw) => {
      const entityTypeIdToEntitySet = {};
      const allEntitySets = allEntitySetsRaw.filter(entitySet => entitySet);
      allEntitySets.forEach((entitySet) => {
        if (entityTypeIdToEntitySet[entitySet.entityTypeId]) {
          entityTypeIdToEntitySet[entitySet.entityTypeId].push(entitySet);
        }
        else {
          entityTypeIdToEntitySet[entitySet.entityTypeId] = [entitySet];
        }
      });
      this.setState({ allEntitySets, entityTypeIdToEntitySet });
    }).catch(() => {
      this.setState({ loadEntitySetsError: true });
    });
  }

  renderExistingLinks = () => {
    return this.state.links.map((link) => {
      let entitySetsString = '';
      if (link.entitySets.length >= 1) {
        entitySetsString = link.entitySets[0].name;
      }
      if (link.entitySets.length > 1) {
        for (let i = 1; i < link.entitySets.length; i += 1) {
          entitySetsString = entitySetsString.concat(', ').concat(link.entitySets[i].name);
        }
      }
      return (

            <button
                className={buttonStyles.deleteButton}
                onClick={() => {
                  this.removeLink(link.propertyType.id);
                }}>-</button>

          <td className={`${styles.propertyTypeSelect} ${styles.linkBox}`}>{link.propertyType.title}
          <td className={`${styles.entitySetsSelect} ${styles.linkBox}`}>{entitySetsString}

      );
    });
  }

  addRow = () => {
    this.setState({ newRow: true });
  }

  removeLink = (propertyTypeId) => {
    const links = this.state.links.filter((link) => {
      return link.propertyType.id !== propertyTypeId;
    });
    this.setState({ links });
  }

  renderAddRowButton = () => {
    if (this.state.newRow) return null;
    return (

          <button className={buttonStyles.addButton} onClick={this.addRow}>+</button>

    );
  }

  onPropertyTypeLinkChange = (e) => {
    const editingPropertyType = (e && e.value) ? e.value : '';
    this.setState({
      editingPropertyType,
      editingEntitySets: [],
      needsOneEntitySetError: false
    });
  }

  onEntitySetsLinkChange = (options) => {
    const editingEntitySets = options.map((option) => {
      return option.value;
    });
    this.setState({ editingEntitySets });
  }

  getPropertyTypeOptions() {
    const availablePropertyTypes = Object.assign({}, this.state.availablePropertyTypes);
    this.state.links.forEach((link) => {
      delete availablePropertyTypes[link.propertyType.id];
    });
    return Object.values(availablePropertyTypes).map((propertyType) => {
      return { label: propertyType.title, value: propertyType.id };
    });
  }

  getEntitySetsOptions = () => {
    const entitySetOptions = [];
    if (this.state.editingPropertyType && this.state.editingPropertyType.length > 0) {
      const entityTypes = this.state.propertyTypeIdToEntityType[this.state.editingPropertyType];
      entityTypes.forEach((entityType) => {
        const entitySets = this.state.entityTypeIdToEntitySet[entityType.id];
        entitySets.forEach((entitySet) => {
          if (this.state.selectedEntitySetIds.includes(entitySet.id)) {
            entitySetOptions.push({ label: entitySet.name, value: entitySet.id });
          }
        });
      });
    }
    return entitySetOptions;
  }

  saveLink = () => {
    if (this.state.editingEntitySets.length < 1) {
      this.setState({ needsOneEntitySetError: true });
      return;
    }
    const propertyType = this.state.availablePropertyTypes[this.state.editingPropertyType];
    const entitySets = this.state.allEntitySets.filter((entitySet) => {
      return this.state.editingEntitySets.includes(entitySet.id);
    });
    const links = this.state.links;
    links.push({ propertyType, entitySets });
    this.setState({
      links,
      newRow: false,
      editingPropertyType: '',
      editingEntitySets: [],
      needsOneEntitySetError: false
    });
  }

  renderNewLink = () => {
    if (this.state.newRow) {
      return (

            <Select
                className={styles.propertyTypeSelect}
                options={this.getPropertyTypeOptions()}
                value={this.state.editingPropertyType}
                onChange={this.onPropertyTypeLinkChange} />

            <Select
                className={styles.entitySetsSelect}
                options={this.getEntitySetsOptions()}
                value={this.state.editingEntitySets}
                onChange={this.onEntitySetsLinkChange}
                multi />

            <Button bsStyle="info" onClick={this.saveLink} className={styles.spacerLeft}>Add links</Button>

      );
    }
    return null;
  }

  renderNotEnoughEntitySetsError = () => {
    if (this.state.needsOneEntitySetError) {
      return
You must specify an entity set to link on.
;
    }
    return null;
  }

  chooseLinks = () => {
    this.setState({
      chooseLinks: true,
      newRow: true
    });
  }

  renderChooseLinksButton = () => {
    if (this.state.selectedEntitySetIds.length < 1 || this.state.chooseLinks) return null;
    return (

        <Button
            bsStyle="primary"
            onClick={this.chooseLinks}
            className={styles.createEntityTypeButton}>
          {'Define the links to join the entity sets'}</Button>

    );
  }

  renderChooseLinks = () => {
    if (!this.state.chooseLinks) return null;
    return (

Step 2. Choose property types to link.

              Property Type
              Entity Sets

            {this.renderExistingLinks()}
            {this.renderNewLink()}
            {this.renderAddRowButton()}

        {this.renderNotEnoughEntitySetsError()}

    );
  }

  loadPropertyTypes = (entityTypes) => {
    const propertyTypeIdToEntityType = {};
    const propertyTypeIds = new Set();
    entityTypes.forEach((entityType) => {
      entityType.properties.forEach((propertyTypeId) => {
        propertyTypeIds.add(propertyTypeId);
        if (propertyTypeIdToEntityType[propertyTypeId]) {
          propertyTypeIdToEntityType[propertyTypeId].push(entityType);
        }
        else {
          propertyTypeIdToEntityType[propertyTypeId] = [entityType];
        }
      });
    });
    Promise.map(propertyTypeIds, (propertyTypeId) => {
      return EntityDataModelApi.getPropertyType(propertyTypeId);
    }).then((propertyTypes) => {
      const availablePropertyTypes = {};
      propertyTypes.forEach((propertyType) => {
        availablePropertyTypes[propertyType.id] = propertyType;
      });
      this.setState({
        availablePropertyTypes,
        propertyTypeIdToEntityType,
        editingPropertyType: '',
        editingEntitySets: [],
        links: [],
        newRow: true,
        needsOneEntitySetError: false,
        loadEntitySetsError: false
      });
    }).catch(() => {
      this.setState({ loadEntitySetsError: true });
    });
  }

  loadPropertyTypesForSelectedEntitySets = (selectedEntitySetIds) => {
    const entityTypeIds = new Set();
    selectedEntitySetIds.forEach((entitySetId) => {
      const entitySet = this.state.allEntitySets.filter((entitySetObj) => {
        return entitySetObj.id === entitySetId;
      })[0];
      entityTypeIds.add(entitySet.entityTypeId);
    });
    Promise.map(entityTypeIds, (entityTypeId) => {
      return EntityDataModelApi.getEntityType(entityTypeId);
    }).then((entityTypes) => {
      this.loadPropertyTypes(entityTypes);
    }).catch(() => {
      this.setState({ loadEntitySetsError: true });
    });
  }

  onSelectedEntitySetChange = (options) => {
    const selectedEntitySetIds = options.map((option) => {
      return option.value;
    });
    const chooseLinks = (selectedEntitySetIds.length < 1) ? false : this.state.chooseLinks;
    this.setState({
      selectedEntitySetIds,
      chooseLinks,
      chooseLinkedEntityType: false
    });
    this.loadPropertyTypesForSelectedEntitySets(selectedEntitySetIds);
  }

  renderLoadEntitySetsError = () => {
    if (this.state.loadEntitySetsError) {
      return (
Unable to load entity sets.
);
    }
    return null;
  }

  renderChooseEntitySets = () => {
    const entitySetOptions = this.state.allEntitySets.map((entitySet) => {
      return ({ label: entitySet.name, value: entitySet.id });
    });
    return (

Step 1. Choose entity sets to link.

        <Select
            value={this.state.selectedEntitySetIds}
            options={entitySetOptions}
            multi
            onChange={this.onSelectedEntitySetChange} />
        {this.renderLoadEntitySetsError()}

    );
  }

  renderDefineLinkedEntityTypeButton = () => {
    if (this.state.selectedEntitySetIds.length > 0
      && this.state.links.length >= 1
      && !this.state.chooseLinkedEntityType) {
      return (

          <Button
              bsStyle="primary"
              onClick={this.chooseLinkedEntityType}
              className={styles.createEntityTypeButton}>
            {'Define the linked entity type to create'}</Button>

      );
    }
    return null;
  }

  renderDefineLinkedEntityType = () => {
    if (this.state.chooseLinkedEntityType) {
      return (
        <DefineLinkedEntityType
            availablePropertyTypes={this.state.availablePropertyTypes}
            linkFn={this.createLinkingEntityType} />
      );
    }
    return null;
  }

  chooseLinkedEntityType = () => {
    this.setState({ chooseLinkedEntityType: true });
  }

  performLink = (name, title, description) => {
    const entitySet = {
      name,
      title,
      description,
      entityTypeId: this.state.linkingEntityTypeId
    };
    const linkingProperties = this.state.links.map((link) => {
      const propertyMap = {};
      link.entitySets.forEach((linkEntitySet) => {
        propertyMap[linkEntitySet.id] = link.propertyType.id;
      });
      return propertyMap;
    });
    const linkingEntitySet = { entitySet, linkingProperties };
    LinkingApi.linkEntitySets(linkingEntitySet)
    .then(() => {
      this.setState({
        linkingSuccess: true,
        linkingError: false
      });
    }).catch(() => {
      this.setState({
        linkingError: true,
        linkingSuccess: false
      });
    });
  }

  createLinkingEntityType = (entityType, deidentified) => {
    const entityTypeIds = new Set();
    this.state.allEntitySets.forEach((entitySet) => {
      if (this.state.selectedEntitySetIds.includes(entitySet.id)) {
        entityTypeIds.add(entitySet.entityTypeId);
      }
    });
    LinkingApi.createLinkingEntityType({ entityType, entityTypeIds, deidentified })
    .then((linkingEntityTypeId) => {
      if (linkingEntityTypeId) {
        this.setState({
          entityTypeCreated: true,
          deidentified,
          linkingEntityTypeId
        });
      }
      else {
        this.setState({
          entityTypeCreated: false,
          linkingError: true,
          linkingSuccess: false
        });
      }
    }).catch(() => {
      this.setState({
        entityTypeCreated: false,
        linkingError: true,
        linkingSuccess: false
      });
    });
  }

  renderLinkingStatus = () => {
    if (this.state.linkingSuccess) {
      return
Success! Your linked entity set is being created.
;
    }
    else if (this.state.linkingError) {
      return
Unable to link entity sets.
;
    }
    return null;
  }

  render() {
    let content;
    if (this.state.linkingSuccess) {
      content =
Success! Your linked entity set is being created.
;
    }
    else if (this.state.entityTypeCreated) {
      content = <DefineLinkedEntitySet linkFn={this.performLink} />;
    }
    else {
      content = (

          {this.renderChooseEntitySets()}
          {this.renderChooseLinksButton()}
          {this.renderChooseLinks()}
          {this.renderDefineLinkedEntityTypeButton()}
          {this.renderDefineLinkedEntityType()}
          {this.renderLinkingStatus()}

      );
    }
    return (
      <Page>
        <Page.Header>
          <Page.Title>Link</Page.Title>
        </Page.Header>
        <Page.Body>{content}</Page.Body>
      </Page>
    );
  }
}

export default Link;
<|endoftext|>


<commit_before>function startApp() {
    showHideMenuLinks();
    showHomeView();

    // Bind the navigation menu links
    $("#linkHome").click(showHomeView);
    $("#linkLogin").click(showLoginView);
    $("#linkRegister").click(showRegisterView);
    $("#linkListAds").click(listAdverts);
    $("#linkCreateAd").click(showCreateAdView);
    $("#linkLogout").click(logoutUser);

    // Bind the form submit buttons
    $("#buttonLoginUser").click(loginUser);
    $("#buttonRegisterUser").click(registerUser);
    $("#buttonCreateAd").click(createAdvert);
    $("#buttonEditAd").click(editAdvert);

    const kinveyBaseUrl = "https://mock.api.com/";
    const kinveyAppKey = "kid_rk";
    const kinveyAppSecret = "736804a668";

    function showView(viewName) {
        // Hide all views and show the selected view only
        $('main > section').hide();
        $('#' + viewName).show();
    }

    function showHideMenuLinks() {
        $("#linkHome").show();
        if (sessionStorage.getItem('authToken') === null) {
            // No logged in user
            $("#linkLogin").show();
            $("#linkRegister").show();
            $("#linkListAds").hide();
            $("#linkCreateAd").hide();
            $("#linkLogout").hide();
        } else {
            // We have logged in user
            $("#linkLogin").hide();
            $("#linkRegister").hide();
            $("#linkListAds").show();
            $("#linkCreateAd").show();
            $("#linkLogout").show();
        }
    }

    function showHomeView() {
        showView('viewHome');
    }

    function showLoginView() {
        showView('viewLogin');
        $('#formLogin').trigger('reset');
    }

    function showRegisterView() {
        $('#formRegister').trigger('reset');
        showView('viewRegister');
    }
    function showCreateAdView() {
        $('#formCreateAd').trigger('reset');
        showView('viewCreateAd');
    }

    // user/login
    function loginUser() {
        const kinveyLoginUrl = kinveyBaseUrl + "user/" + kinveyAppKey + "/login";
        const kinveyAuthHeaders = {
            'Authorization': "Basic " + btoa(kinveyAppKey + ":" + kinveyAppSecret),
        };
        let userData = {
            username: $('#formLogin input[name=username]').val(),
            password: $('#formLogin input[name=passwd]').val()
        };

        $.ajax({
            method: "POST",
            url: kinveyLoginUrl,
            headers: kinveyAuthHeaders,
            data: userData,
            success: loginSuccess
        });

        function loginSuccess(userInfo) {
            saveAuthInSession(userInfo);
            showHideMenuLinks();
            listAdverts();
        }
    }

    function saveAuthInSession(userInfo) {
        let userAuth = userInfo._kmd.authtoken;
        sessionStorage.setItem('authToken', userAuth);
        let userId = userInfo._id;
        sessionStorage.setItem('userId', userId);
    }

    // user/register
    function registerUser() {
        const kinveyRegisterUrl = kinveyBaseUrl + "user/" + kinveyAppKey + "/";
        const kinveyAuthHeaders = {
            'Authorization': "Basic " + btoa(kinveyAppKey + ":" + kinveyAppSecret),
        };

        let userData = {
            username: $('#formRegister input[name=username]').val(),
            password: $('#formRegister input[name=passwd]').val()
        };

        $.ajax({
            method: "POST",
            url: kinveyRegisterUrl,
            headers: kinveyAuthHeaders,
            data: userData,
            success: registerSuccess
        });

        function registerSuccess(userInfo) {
            console.log(userInfo);
            saveAuthInSession(userInfo);
            showHideMenuLinks();
            listAdverts();
        }
    }

    // user/logout
    function logoutUser() {
        sessionStorage.clear();
        $('#loggedInUser').text("");
        showHideMenuLinks();
        showHomeView();
    }

    // advertisement/all
    function listAdverts() {
        $('#ads').empty();
        showView('viewAds');

        const kinveyAdvertsUrl = kinveyBaseUrl + "appdata/" + kinveyAppKey + "/adverts";
        const kinveyAuthHeaders = {
            'Authorization': "Kinvey " + sessionStorage.getItem('authToken'),
        };
        $.ajax({
            method: "GET",
            url: kinveyAdvertsUrl,
            headers: kinveyAuthHeaders,
            success: loadAdvertsSuccess
        });

        function loadAdvertsSuccess(adverts) {
            if (adverts.length === 0) {
                $('#ads').text('No advertisements available.');
            } else {
                let advertsTable = $('
')
                    .append($('
').append(
                        'Title	',
                        'Publisher	',
                        'Date Published	',
                        'Price	',
                        'Actions	'
                        )
                    );

                for (let advert of adverts) {
                    let links = [];

                    if (advert._acl.creator == sessionStorage['userId']) {
                        let deleteLink = $(`<a data-id="${advert._id}" href="#">[Delete]</a>`)
                            .click(function() { deleteAdvert($(this).attr("data-id")) });
                        let editLink = $(`<a data-id="${advert._id}" href="#">[Edit]</a>`)
                            .click(function() { loadAdvertForEdit($(this).attr("data-id")) });
                        links = [deleteLink, ' ', editLink];
                    }

                    advertsTable.append($('
').append(
                        $('').text(advert.title),
                        $('').text(advert.publisher),
                        $('').text(advert.datePublished),
                        $('').text(advert.price),
                        $('').append(links)
                    ));
                }

                $('#ads').append(advertsTable);
            }
        }
    }
    // advertisement/create
    function createAdvert() {
        const kinveyAuthHeaders = {
            'Authorization': "Kinvey " + sessionStorage.getItem('authToken'),
        };

        const kinveyUserUrl =
            `${kinveyBaseUrl}user/${kinveyAppKey}/${sessionStorage.getItem('userId')}`;

        $.ajax({
            method: "GET",
            url: kinveyUserUrl,
            headers: kinveyAuthHeaders,
            success: afterPublisherRequest
        });

        function afterPublisherRequest(publisher) {
            let advertData = {
                title: $('#formCreateAd input[name=title]').val(),
                publisher: publisher.username,
                datePublished: $('#formCreateAd input[name=datePublished]').val(),
                price: Number($('#formCreateAd input[name=price]').val())
            };

            const kinveyAdvertsUrl = kinveyBaseUrl + "appdata/" + kinveyAppKey + "/adverts";
            $.ajax({
                method: "POST",
                url: kinveyAdvertsUrl,
                headers: kinveyAuthHeaders,
                data: advertData,
                success: createAdvertSuccess
            });

            function createAdvertSuccess(response) {
                listAdverts();
            }
        }
    }

    // advertisement/delete
    function deleteAdvert(advertId) {
        const kinveyBookUrl = kinveyBaseUrl + "appdata/" +
            kinveyAppKey + "/adverts/" + advertId;
        const kinveyAuthHeaders = {
            'Authorization': "Kinvey " + sessionStorage.getItem('authToken'),
        };

        $.ajax({
            method: "DELETE",
            url: kinveyBookUrl,
            headers: kinveyAuthHeaders,
            success: deleteBookSuccess
        });

        function deleteBookSuccess(response) {
            listAdverts();
        }
    }

    // advertisement/edit GET
    function loadAdvertForEdit(advertId) {
        const kinveyBookUrl = kinveyBaseUrl + "appdata/" +
            kinveyAppKey + "/adverts/" + advertId;
        const kinveyAuthHeaders = {
            'Authorization': "Kinvey " + sessionStorage.getItem('authToken'),
        };

        $.ajax({
            method: "GET",
            url: kinveyBookUrl,
            headers: kinveyAuthHeaders,
            success: loadAdvertForEditSuccess
        });

        function loadAdvertForEditSuccess(advert) {
            $('#formEditAd input[name=id]').val(advert._id);
            $('#formEditAd input[name=title]').val(advert.title);
            $('#formEditAd input[name=publisher]').val(advert.publisher);
            $('#formEditAd input[name=datePublished]').val(advert.datePublished);
            $('#formEditAd input[name=price]').val(advert.price);
            showView('viewEditAd');
        }
    }

    // advertisement/edit POST
    function editAdvert() {
        const kinveyAdvertUrl =  kinveyBaseUrl + "appdata/" + kinveyAppKey +
            "/adverts/" + $('#formEditAd input[name=id]').val();
        const kinveyAuthHeaders = {
            'Authorization': "Kinvey " + sessionStorage.getItem('authToken'),
        };

        let advertData = {
            title: $('#formEditAd input[name=title]').val(),
            publisher: $('#formEditAd input[name=publisher]').val(),
            datePublished: $('#formEditAd input[name=datePublished]').val(),
            price: $('#formEditAd input[name=price]').val()
        };

        $.ajax({
            method: "PUT",
            url: kinveyAdvertUrl,
            headers: kinveyAuthHeaders,
            data: advertData,
            success: editAdvertSuccess
        });

        function editAdvertSuccess(response) {
            listAdverts();
        }
    }

}
<commit_msg>little help for 7.2<commit_after>function startApp() {
    showHideMenuLinks();
    showHomeView();

    // Bind the navigation menu links
    $("#linkHome").click(showHomeView);
    $("#linkLogin").click(showLoginView);
    $("#linkRegister").click(showRegisterView);
    $("#linkListAds").click(listAdverts);
    $("#linkCreateAd").click(showCreateAdView);
    $("#linkLogout").click(logoutUser);

    // Bind the form submit buttons
    $("#buttonLoginUser").click(loginUser);
    $("#buttonRegisterUser").click(registerUser);
    $("#buttonCreateAd").click(createAdvert);
    $("#buttonEditAd").click(editAdvert);

    const kinveyBaseUrl = "https://mock.api.com/";
    const kinveyAppKey = "kid_rk";
    const kinveyAppSecret = "736804a668";

    function showView(viewName) {
        // Hide all views and show the selected view only
        $('main > section').hide();
        $('#' + viewName).show();
    }

    function showHideMenuLinks() {
        $("#linkHome").show();
        if (sessionStorage.getItem('authToken') === null) {
            // No logged in user
            $("#linkLogin").show();
            $("#linkRegister").show();
            $("#linkListAds").hide();
            $("#linkCreateAd").hide();
            $("#linkLogout").hide();
        } else {
            // We have logged in user
            $("#linkLogin").hide();
            $("#linkRegister").hide();
            $("#linkListAds").show();
            $("#linkCreateAd").show();
            $("#linkLogout").show();
        }
    }

    function showHomeView() {
        showView('viewHome');
    }

    function showLoginView() {
        showView('viewLogin');
        $('#formLogin').trigger('reset');
    }

    function showRegisterView() {
        $('#formRegister').trigger('reset');
        showView('viewRegister');
    }
    function showCreateAdView() {
        $('#formCreateAd').trigger('reset');
        showView('viewCreateAd');
    }

    // user/login
    function loginUser() {
        const kinveyLoginUrl = kinveyBaseUrl + "user/" + kinveyAppKey + "/login";
        const kinveyAuthHeaders = {
            'Authorization': "Basic " + btoa(kinveyAppKey + ":" + kinveyAppSecret),
        };
        let userData = {
            username: $('#formLogin input[name=username]').val(),
            password: $('#formLogin input[name=passwd]').val()
        };

        $.ajax({
            method: "POST",
            url: kinveyLoginUrl,
            headers: kinveyAuthHeaders,
            data: userData,
            success: loginSuccess
        });

        function loginSuccess(userInfo) {
            saveAuthInSession(userInfo);
            showHideMenuLinks();
            listAdverts();
        }
    }

    function saveAuthInSession(userInfo) {
        let userAuth = userInfo._kmd.authtoken;
        sessionStorage.setItem('authToken', userAuth);
        let userId = userInfo._id;
        sessionStorage.setItem('userId', userId);
    }

    // user/register
    function registerUser() {
        const kinveyRegisterUrl = kinveyBaseUrl + "user/" + kinveyAppKey + "/";
        const kinveyAuthHeaders = {
            'Authorization': "Basic " + btoa(kinveyAppKey + ":" + kinveyAppSecret),
        };

        let userData = {
            username: $('#formRegister input[name=username]').val(),
            password: $('#formRegister input[name=passwd]').val()
        };

        $.ajax({
            method: "POST",
            url: kinveyRegisterUrl,
            headers: kinveyAuthHeaders,
            data: userData,
            success: registerSuccess
        });

        function registerSuccess(userInfo) {
            console.log(userInfo);
            saveAuthInSession(userInfo);
            showHideMenuLinks();
            listAdverts();
        }
    }

    // user/logout
    function logoutUser() {
        sessionStorage.clear();
        $('#loggedInUser').text("");
        showHideMenuLinks();
        showHomeView();
    }

    // advertisement/all
    function listAdverts() {
        $('#ads').empty();
        showView('viewAds');

        const kinveyAdvertsUrl = kinveyBaseUrl + "appdata/" + kinveyAppKey + "/adverts";
        const kinveyAuthHeaders = {
            'Authorization': "Kinvey " + sessionStorage.getItem('authToken'),
        };
        $.ajax({
            method: "GET",
            url: kinveyAdvertsUrl,
            headers: kinveyAuthHeaders,
            success: loadAdvertsSuccess
        });

        function loadAdvertsSuccess(adverts) {
            if (adverts.length === 0) {
                $('#ads').text('No advertisements available.');
            } else {
                let advertsTable = $('
')
                    .append($('
').append(
                        'Title	',
                        'Publisher	',
                        'Date Published	',
                        'Price	',
                        'Actions	'
                        )
                    );

                for (let advert of adverts) {
                    let links = [];

                    if (advert._acl.creator == sessionStorage['userId']) {
                        let deleteLink = $(`<a data-id="${advert._id}" href="#">[Delete]</a>`)
                            .click(function() { deleteAdvert($(this).attr("data-id")) });
                        let editLink = $(`<a data-id="${advert._id}" href="#">[Edit]</a>`)
                            .click(function() { loadAdvertForEdit($(this).attr("data-id")) });
                        links = [deleteLink, ' ', editLink];
                    }

                    advertsTable.append($('
').append(
                        $('').text(advert.title),
                        $('').text(advert.publisher),
                        $('').text(advert.datePublished),
                        $('').text(advert.price),
                        $('').append(links)
                    ));
                }

                $('#ads').append(advertsTable);
            }
        }
    }
    // advertisement/create
    function createAdvert() {
        const kinveyAuthHeaders = {
            'Authorization': "Kinvey " + sessionStorage.getItem('authToken'),
        };

        const kinveyUserUrl =
            `${kinveyBaseUrl}user/${kinveyAppKey}/${sessionStorage.getItem('userId')}`;

        $.ajax({
            method: "GET",
            url: kinveyUserUrl,
            headers: kinveyAuthHeaders,
            success: afterPublisherRequest,
            error: showError
        });

        function afterPublisherRequest(publisher) {
            let advertData = {
                title: $('#formCreateAd input[name=title]').val(),
                publisher: publisher.username,
                datePublished: $('#formCreateAd input[name=datePublished]').val(),
                price: Number($('#formCreateAd input[name=price]').val())
            };

            const kinveyAdvertsUrl = kinveyBaseUrl + "appdata/" + kinveyAppKey + "/adverts";
            $.ajax({
                method: "POST",
                url: kinveyAdvertsUrl,
                headers: kinveyAuthHeaders,
                data: advertData,
                success: createAdvertSuccess,
                error: handleAjaxError
            });

            function createAdvertSuccess(response) {
                listAdverts();
				showInfo('Advertisement created.');
            }
        }
    }

    // advertisement/delete
    function deleteAdvert(advertId) {
        const kinveyBookUrl = kinveyBaseUrl + "appdata/" +
            kinveyAppKey + "/adverts/" + advertId;
        const kinveyAuthHeaders = {
            'Authorization': "Kinvey " + sessionStorage.getItem('authToken'),
        };

        $.ajax({
            method: "DELETE",
            url: kinveyBookUrl,
            headers: kinveyAuthHeaders,
            success: deleteBookSuccess,
            error: handleAjaxError
        });

        function deleteBookSuccess(response) {
            listAdverts();
			showInfo('Advert deleted.');
        }
    }

    // advertisement/edit GET
    function loadAdvertForEdit(advertId) {
        const kinveyBookUrl = kinveyBaseUrl + "appdata/" +
            kinveyAppKey + "/adverts/" + advertId;
        const kinveyAuthHeaders = {
            'Authorization': "Kinvey " + sessionStorage.getItem('authToken'),
        };

        $.ajax({
            method: "GET",
            url: kinveyBookUrl,
            headers: kinveyAuthHeaders,
            success: loadAdvertForEditSuccess,
            error: handleAjaxError
        });

        function loadAdvertForEditSuccess(advert) {
            $('#formEditAd input[name=id]').val(advert._id);
            $('#formEditAd input[name=title]').val(advert.title);
            $('#formEditAd input[name=publisher]').val(advert.publisher);
            $('#formEditAd input[name=datePublished]').val(advert.datePublished);
            $('#formEditAd input[name=price]').val(advert.price);
            showView('viewEditAd');
        }
    }

    // advertisement/edit POST
    function editAdvert() {
        const kinveyAdvertUrl =  kinveyBaseUrl + "appdata/" + kinveyAppKey +
            "/adverts/" + $('#formEditAd input[name=id]').val();
        const kinveyAuthHeaders = {
            'Authorization': "Kinvey " + sessionStorage.getItem('authToken'),
        };

        let advertData = {
            title: $('#formEditAd input[name=title]').val(),
            publisher: $('#formEditAd input[name=publisher]').val(),
            datePublished: $('#formEditAd input[name=datePublished]').val(),
            price: $('#formEditAd input[name=price]').val()
        };

        $.ajax({
            method: "PUT",
            url: kinveyAdvertUrl,
            headers: kinveyAuthHeaders,
            data: advertData,
            success: editAdvertSuccess,
            error: handleAjaxError
        });

        function editAdvertSuccess(response) {
            listAdverts();
            showInfo('Advertisement edited.');
        }
    }

}
<|endoftext|>


<commit_before>/**
 * @author mrdoob / http://mrdoob.com/
 */

THREE.GridHelper = function ( size, segments, color1, color2 ) {

	segments = segments || 1;
	color1 = new THREE.Color( color1 !== undefined ? color1 : 0x444444 );
	color2 = new THREE.Color( color2 !== undefined ? color2 : 0x888888 );

	var center = segments / 2;
	var step = ( size * 2 ) / segments;
	var vertices = [], colors = [];

	for ( var i = 0, j = 0, k = - size; i <= segments; i ++, k += step ) {

		vertices.push( - size, 0, k, size, 0, k );
		vertices.push( k, 0, - size, k, 0, size );

		var color = i === center ? color1 : color2;

		color.toArray( colors, j ); j += 3;
		color.toArray( colors, j ); j += 3;
		color.toArray( colors, j ); j += 3;
		color.toArray( colors, j ); j += 3;

	}

	var geometry = new THREE.BufferGeometry();
	geometry.addAttribute( 'position', new THREE.Float32Attribute( vertices, 3 ) );
	geometry.addAttribute( 'color', new THREE.Float32Attribute( colors, 3 ) );

	var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } );

	THREE.LineSegments.call( this, geometry, material );

};

THREE.GridHelper.prototype = Object.create( THREE.LineSegments.prototype );
THREE.GridHelper.prototype.constructor = THREE.GridHelper;

THREE.GridHelper.prototype.setColors = function () {

	console.error( 'THREE.GridHelper: setColors() has been deprecated, pass them in the constructor instead.' );

};
<commit_msg>GridHelper: Renamed segments to divisions. See #9193.<commit_after>/**
 * @author mrdoob / http://mrdoob.com/
 */

THREE.GridHelper = function ( size, divisions, color1, color2 ) {

	divisions = divisions || 1;
	color1 = new THREE.Color( color1 !== undefined ? color1 : 0x444444 );
	color2 = new THREE.Color( color2 !== undefined ? color2 : 0x888888 );

	var center = divisions / 2;
	var step = ( size * 2 ) / divisions;
	var vertices = [], colors = [];

	for ( var i = 0, j = 0, k = - size; i <= divisions; i ++, k += step ) {

		vertices.push( - size, 0, k, size, 0, k );
		vertices.push( k, 0, - size, k, 0, size );

		var color = i === center ? color1 : color2;

		color.toArray( colors, j ); j += 3;
		color.toArray( colors, j ); j += 3;
		color.toArray( colors, j ); j += 3;
		color.toArray( colors, j ); j += 3;

	}

	var geometry = new THREE.BufferGeometry();
	geometry.addAttribute( 'position', new THREE.Float32Attribute( vertices, 3 ) );
	geometry.addAttribute( 'color', new THREE.Float32Attribute( colors, 3 ) );

	var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } );

	THREE.LineSegments.call( this, geometry, material );

};

THREE.GridHelper.prototype = Object.create( THREE.LineSegments.prototype );
THREE.GridHelper.prototype.constructor = THREE.GridHelper;

THREE.GridHelper.prototype.setColors = function () {

	console.error( 'THREE.GridHelper: setColors() has been deprecated, pass them in the constructor instead.' );

};
<|endoftext|>