<commit_before>/*
 * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Cloudkick licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var uuid = require('node-uuid');

var Errorf = require('util/misc').Errorf;

/**
 * A resource that is backed by a directory.
 * @param {String} name The name of this resource's directory.
 * @constructor
 */
function JobManager() {
  this._resourceManagers = {};
  this._jobs = {};
}

/**
 * Queue a job to be run on the appropriate resource.
 * @param {Object} job The job to be executed.
 */
JobManager.prototype.run = function(job) {
  var type = job.resourceType.constructor.name;

  if (!this._resourceManagers[type]) {
    throw new Errorf('No resource manager registered for \'%s\'', type);
  }

  // Assign the job an ID
  job.id = uuid();
  this._jobs[job.id] = job;

  var manager = this._resourceManagers[type];
  manager.runJob(job);
};

/**
 * Retrieve a job based on its id.
 */
JobManager.prototype.getJob = function(id) {
  return this._jobs[id];
};

/**
 * Register a new resource manager.
 * @param {Object} manager The resource manager.
 */
JobManager.prototype.registerResourceManager = function(manager) {
  var type = manager.resourceType.constructor.name;
  this._resourceManagers[type] = manager;
};

exports.JobManager = JobManager;
<commit_msg>fix bug in job manager registration<commit_after>/*
 * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Cloudkick licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var uuid = require('node-uuid');

var Errorf = require('util/misc').Errorf;

/**
 * A resource that is backed by a directory.
 * @param {String} name The name of this resource's directory.
 * @constructor
 */
function JobManager() {
  this._resourceManagers = {};
  this._jobs = {};
}

/**
 * Queue a job to be run on the appropriate resource.
 * @param {Object} job The job to be executed.
 */
JobManager.prototype.run = function(job) {
  var type = job.resourceType.name;

  if (!this._resourceManagers[type]) {
    throw new Errorf('No resource manager registered for \'%s\'', type);
  }

  // Assign the job an ID
  job.id = uuid();
  this._jobs[job.id] = job;

  var manager = this._resourceManagers[type];
  manager.runJob(job);
};

/**
 * Retrieve a job based on its id.
 */
JobManager.prototype.getJob = function(id) {
  return this._jobs[id];
};

/**
 * Register a new resource manager.
 * @param {Object} manager The resource manager.
 */
JobManager.prototype.registerResourceManager = function(manager) {
  var type = manager.resourceType.name;
  this._resourceManagers[type] = manager;
};

exports.JobManager = JobManager;
<|endoftext|>


<commit_before>var $$ = module.exports = {};

// UTILITY METHODS
////////////////////////////////////////////////////////////////////////////////

// returns object with exported properties of all required modules
// example: var __ = $$.import('errors', 'nodes');
$$.import = function import() {
  var box = {}, i, each;

  each = function (src) {
    var mod = require('./' + src);
    Object.getOwnPropertyNames(mod).forEach(function (prop) {
      box[prop] = mod[prop];
    });
  };

  for (i = 0; i < arguments.length; i++) {
    each(arguments[i]);
  }

  return box;
};

// iterates through all object keys-value pairs calling iterator on each one
// example: $$.each(hash, function (val, key) { /* ... */ });
$$.each = function each(obj, iterator, context) {
  var keys = Object.getOwnPropertyNames(obj), i, l;

  context = context || iterator;

  for (i = 0, l = keys.length; i < l; i++) {
    iterator.call(context, obj[keys[i]], keys[i], obj);
  }
};

// CLASSES
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// vim:ts=2:sw=2
////////////////////////////////////////////////////////////////////////////////
<commit_msg>Added $$.extend<commit_after>var $$ = module.exports = {};

// UTILITY METHODS
////////////////////////////////////////////////////////////////////////////////

// returns object with exported properties of all required modules
// example: var __ = $$.import('errors', 'nodes');
$$.import = function import() {
  var box = {}, i, each;

  each = function (src) {
    var mod = require('./' + src);
    Object.getOwnPropertyNames(mod).forEach(function (prop) {
      box[prop] = mod[prop];
    });
  };

  for (i = 0; i < arguments.length; i++) {
    each(arguments[i]);
  }

  return box;
};

// iterates through all object keys-value pairs calling iterator on each one
// example: $$.each(hash, function (val, key) { /* ... */ });
$$.each = function each(obj, iterator, context) {
  var keys = Object.getOwnPropertyNames(obj), i, l;

  context = context || iterator;

  for (i = 0, l = keys.length; i < l; i++) {
    iterator.call(context, obj[keys[i]], keys[i], obj);
  }
};

// <object> $$.extend(target, *sources)
//
// Copy all of the properties in the source objects over to the target object.
// It's in-order, so the last source will override properties of the same name
// in previous arguments.
//
// Example: var o = $$.extend({}, a, b, c);
$$.extend = function extend() {
  var target = arguments[0] || {}, i, l;

  for (i = 1, l = arguments.length; i < l; i++) {
    if (undefined !== arguments[i] && null !== arguments[i]) {
      Object.getOwnPropertyNames(arguments[i]).forEach(function (key) {
        target[key] = arguments[i][key];
      });
    }
  }

  return target;
};

// CLASSES
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// vim:ts=2:sw=2
////////////////////////////////////////////////////////////////////////////////
<|endoftext|>


<commit_before>import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import { BigNumber } from 'meteor/ethereum:web3';
import { web3Obj } from 'meteor/makerotc:dapple';

import Tokens from '/imports/api/tokens';
import { Offers } from '/imports/api/offers';
import { $ } from 'meteor/jquery';

import '/imports/ui/client/shared.js';
import './neworder.html';

Template.neworder.viewmodel({
  share: 'newOffer',
  lastError: '',
  bestOffer: undefined,
  validAmount: true,
  total: '',
  price: '',
  amount: '',
  shouldShowMaxBtn: false,
  type() {
    return this.orderType() ? this.orderType() : '';
  },
  precision() {
    return Dapple.getTokenSpecs(Session.get('baseCurrency')).precision;
  },
  sellCurrency() {
    if (this.type() === 'buy') {
      return Session.get('quoteCurrency');
    }
    return Session.get('baseCurrency');
  },
  onFocus() {
    this.shouldShowMaxBtn(true);
  },
  onBlur() {
    this.shouldShowMaxBtn(false);
  },
  canAutofill() {
    return Session.get('market_open');
  },
  canChangePrice() {
    return Session.get('market_open');
  },
  canChangeAmountAndTotal() {
    const marketOpen = Session.get('market_open');
    if (!marketOpen) return false;
    try {
      const price = new BigNumber(this.price());
      return !price.isNaN() && price.gt(0);
    } catch (e) {
      return false;
    }
  },
  calcTotal() {
    this.validAmount(true);
    if (this.precision() === 0 && this.amount() % 1 !== 0) {
      this.validAmount(false);
      this.total('0');
      return;
    }
    try {
      const price = new BigNumber(this.price());
      const amount = new BigNumber(this.amount());
      const total = price.times(amount);
      if (total.isNaN()) {
        this.total('0');
      } else {
        this.total(total.toString(10));
      }
    } catch (e) {
      this.total('0');
    }
  },
  calcAmount() {
    this.validAmount(true);
    if (this.precision() === 0 && this.total() % 1 !== 0) {
      this.validAmount(false);
      this.amount('0');
      return;
    }
    try {
      const price = new BigNumber(this.price());
      let amount = new BigNumber(this.amount());
      const total = new BigNumber(this.total());
      if (total.isZero() && price.isZero() && (amount.isNaN() || amount.isNegative())) {
        this.amount('0');
      } else if (!total.isZero() || !price.isZero()) {
        amount = total.div(price);
        if (amount.isNaN()) {
          this.amount('0');
        } else {
          this.amount(amount.toString(10));
        }
      }
    } catch (e) {
      this.amount('0');
    }
  },
  maxAmount() {
    let maxAmount = '0';
    if (this.type() === 'sell') {
      const token = Tokens.findOne(Session.get('baseCurrency'));
      if (token) {
        const balance = new BigNumber(token.balance);
        /* const allowance = new BigNumber(token.allowance);
         maxAmount = web3Obj.fromWei(BigNumber.min(balance, allowance).toString(10));*/
        maxAmount = web3Obj.fromWei(balance.toString(10));
      }
    } else {
      maxAmount = '9e999';
    }
    return maxAmount;
  },
  maxTotal() {
    // Only allow change of total if price is well-defined
    try {
      const price = new BigNumber(this.price());
      if ((price.isNaN() || price.isZero() || price.isNegative())) {
        return '0';
      }
    } catch (e) {
      return '0';
    }
    // If price is well-defined, take minimum of balance and allowance of currency, if 'buy', otherwise Infinity
    let maxTotal = '0';
    if (this.type() === 'buy') {
      const token = Tokens.findOne(Session.get('quoteCurrency'));
      if (token) {
        const balance = new BigNumber(token.balance);
        /* const allowance = new BigNumber(token.allowance);
         maxTotal = web3Obj.fromWei(BigNumber.min(balance, allowance).toString(10));*/
        maxTotal = web3Obj.fromWei(balance.toString(10));
      }
    } else {
      maxTotal = '9e999';
    }
    return maxTotal;
  },
  hasBalance(currency) {
    try {
      const token = Tokens.findOne(currency);
      const balance = new BigNumber(token.balance);
      return token && balance.gte(web3Obj.toWei(new BigNumber(this.type() === 'sell' ? this.amount() : this.total())));
    } catch (e) {
      // since we have placeholders the value will be empty string so we have to true its a valid data
      return this.amount() === '' || this.total() === '';
    }
  },
  quoteAvailable() {
    const token = Tokens.findOne(Session.get('quoteCurrency'));
    if (token) {
      return token.balance;
    }
    return 0;
  },
  baseAvailable() {
    const token = Tokens.findOne(Session.get('baseCurrency'));
    if (token) {
      return token.balance;
    }
    return 0;
  },
  quoteAllowance() {
    const token = Tokens.findOne(Session.get('quoteCurrency'));
    if (token) {
      return token.allowance;
    }
    return 0;
  },
  baseAllowance() {
    const token = Tokens.findOne(Session.get('baseCurrency'));
    if (token) {
      return token.allowance;
    }
    return 0;
  },
  sup(event) {
  },

  hasAllowance(currency) {
    try {
      const token = Tokens.findOne(currency);
      const allowance = new BigNumber(token.allowance);
      return token && allowance.gte(web3Obj.toWei(new BigNumber(this.type() === 'sell' ? this.amount() : this.total())));
    } catch (e) {
      return false;
    }
  },
  canSubmit() {
    try {
      const type = this.type();
      const price = new BigNumber(this.price());
      const amount = new BigNumber(this.amount());
      const maxAmount = new BigNumber(this.maxAmount());
      const total = new BigNumber(this.total());
      const maxTotal = new BigNumber(this.maxTotal());
      const marketOpen = Session.get('market_open');
      const validTokenPair = Session.get('quoteCurrency') !== Session.get('baseCurrency');
      return marketOpen && price.gt(0) && amount.gt(0) && total.gt(0) && validTokenPair &&
        (type !== 'buy' || total.lte(maxTotal)) && (type !== 'sell' || amount.lte(maxAmount));
    } catch (e) {
      return false;
    }
  },
  preventDefault(event) {
    event.preventDefault();
  },
  betterOffer() {
    try {
      const quoteCurrency = Session.get('quoteCurrency');
      const baseCurrency = Session.get('baseCurrency');
      const price = new BigNumber(this.price());
      if (price.lte(0) || price.isNaN()) {
        this.bestOffer(undefined);
        return undefined;
      }

      let offer;
      if (this.type() === 'buy') {
        offer = Offers.findOne({ buyWhichToken: quoteCurrency, sellWhichToken: baseCurrency },
          { sort: { ask_price_sort: 1 } });
        if (offer && Object.prototype.hasOwnProperty.call(offer, 'ask_price')
          && price.gt(new BigNumber(offer.ask_price))) {
          this.bestOffer(offer._id);
          return offer;
        }
        this.bestOffer(undefined);
        return undefined;
      } else if (this.type() === 'sell') {
        offer = Offers.findOne({ buyWhichToken: baseCurrency, sellWhichToken: quoteCurrency },
          { sort: { ask_price_sort: 1 } });
        if (offer && Object.prototype.hasOwnProperty.call(offer, 'bid_price')
          && price.lt(new BigNumber(offer.bid_price))) {
          this.bestOffer(offer._id);
          return offer;
        }
      }
    } catch (e) {
      this.bestOffer(undefined);
      return undefined;
    }
    this.bestOffer(undefined);
    return undefined;
  },
  autofill(event) {
    event.preventDefault();
    const marketOpen = Session.get('market_open');
    const quoteCurrency = Session.get('quoteCurrency');
    const baseCurrency = Session.get('baseCurrency');
    let offer;
    let price = 0;
    let available = 0;
    if (!marketOpen) return false;
    if (this.type() === 'buy') {
      offer = Offers.findOne({ buyWhichToken: quoteCurrency, sellWhichToken: baseCurrency },
        { sort: { ask_price_sort: 1 } });
      if (offer && Object.prototype.hasOwnProperty.call(offer, 'ask_price')) {
        price = new BigNumber(offer.ask_price);
        available = web3Obj.fromWei(this.quoteAvailable()).toString(10);
        this.price(price);
        this.total(available);
        this.calcAmount();
      }
    } else if (this.type() === 'sell') {
      offer = Offers.findOne({ buyWhichToken: baseCurrency, sellWhichToken: quoteCurrency },
        { sort: { ask_price_sort: 1 } });
      if (offer && Object.prototype.hasOwnProperty.call(offer, 'bid_price')) {
        price = new BigNumber(offer.bid_price);
        available = web3Obj.fromWei(this.baseAvailable()).toString(10);
        this.price(price);
        this.amount(available);
        this.calcTotal();
      }
    }
    return false;
  },
  openOfferModal() {
    Session.set('selectedOffer', this.bestOffer());
  },
  showConfirmation(event) {
    event.preventDefault();
    this.offerPrice(this.price());
    this.offerAmount(this.amount());
    this.offerTotal(this.total());
    this.offerType(this.type());
  },
  showDepositTab() {
    $('#wrap').tab('show');
  },
  showAllowanceModal(token) {
    $(`#allowanceModal${token}`).modal('show');
  },
});
<commit_msg>Fixing some bugs on buy and sell max buttons<commit_after>import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import { BigNumber } from 'meteor/ethereum:web3';
import { web3Obj } from 'meteor/makerotc:dapple';

import Tokens from '/imports/api/tokens';
import { Offers } from '/imports/api/offers';
import { $ } from 'meteor/jquery';

import '/imports/ui/client/shared.js';
import './neworder.html';

Template.neworder.viewmodel({
  share: 'newOffer',
  lastError: '',
  bestOffer: undefined,
  validAmount: true,
  total: '',
  price: '',
  amount: '',
  shouldShowMaxBtn: false,
  type() {
    return this.orderType() ? this.orderType() : '';
  },
  precision() {
    return Dapple.getTokenSpecs(Session.get('baseCurrency')).precision;
  },
  sellCurrency() {
    if (this.type() === 'buy') {
      return Session.get('quoteCurrency');
    }
    return Session.get('baseCurrency');
  },
  onFocus() {
    this.shouldShowMaxBtn(true);
  },
  onBlur() {
    this.shouldShowMaxBtn(false);
  },
  canAutofill() {
    return Session.get('market_open');
  },
  canChangePrice() {
    return Session.get('market_open');
  },
  canChangeAmountAndTotal() {
    const marketOpen = Session.get('market_open');
    if (!marketOpen) return false;
    try {
      const price = new BigNumber(this.price());
      return !price.isNaN() && price.gt(0);
    } catch (e) {
      return false;
    }
  },
  calcTotal() {
    this.validAmount(true);
    if (this.precision() === 0 && this.amount() % 1 !== 0) {
      this.validAmount(false);
      this.total('0');
      return;
    }
    try {
      const price = new BigNumber(this.price());
      const amount = new BigNumber(this.amount());
      const total = price.times(amount);
      if (total.isNaN()) {
        this.total('0');
      } else {
        this.total(total.toString(10));
      }
    } catch (e) {
      this.total('0');
    }
  },
  calcAmount() {
    this.validAmount(true);
    if (this.precision() === 0 && this.total() % 1 !== 0) {
      this.validAmount(false);
      this.amount('0');
      return;
    }
    try {
      const price = new BigNumber(this.price());
      const total = new BigNumber(this.total());
      if (total.isZero() && price.isZero()) {
        this.amount('0');
      } else {
        const amount = total.div(price);
        if (amount.isNaN()) {
          this.amount('0');
        } else {
          this.amount(amount.toString(10));
        }
      }
    } catch (e) {
      this.amount('0');
    }
  },
  maxAmount() {
    let maxAmount = '0';
    if (this.type() === 'sell') {
      const token = Tokens.findOne(Session.get('baseCurrency'));
      if (token) {
        const balance = new BigNumber(token.balance);
        /* const allowance = new BigNumber(token.allowance);
         maxAmount = web3Obj.fromWei(BigNumber.min(balance, allowance).toString(10));*/
        maxAmount = web3Obj.fromWei(balance.toString(10));
      }
    } else {
      maxAmount = '9e999';
    }
    return maxAmount;
  },
  maxTotal() {
    // Only allow change of total if price is well-defined
    try {
      const price = new BigNumber(this.price());
      if ((price.isNaN() || price.isZero() || price.isNegative())) {
        return '0';
      }
    } catch (e) {
      return '0';
    }
    // If price is well-defined, take minimum of balance and allowance of currency, if 'buy', otherwise Infinity
    let maxTotal = '0';
    if (this.type() === 'buy') {
      const token = Tokens.findOne(Session.get('quoteCurrency'));
      if (token) {
        const balance = new BigNumber(token.balance);
        /* const allowance = new BigNumber(token.allowance);
         maxTotal = web3Obj.fromWei(BigNumber.min(balance, allowance).toString(10));*/
        maxTotal = web3Obj.fromWei(balance.toString(10));
      }
    } else {
      maxTotal = '9e999';
    }
    return maxTotal;
  },
  hasBalance(currency) {
    try {
      const token = Tokens.findOne(currency);
      const balance = new BigNumber(token.balance);
      return token && balance.gte(web3Obj.toWei(new BigNumber(this.type() === 'sell' ? this.amount() : this.total())));
    } catch (e) {
      // since we have placeholders the value will be empty string so we have to true its a valid data
      return this.amount() === '' || this.total() === '';
    }
  },
  quoteAvailable() {
    const token = Tokens.findOne(Session.get('quoteCurrency'));
    if (token) {
      return token.balance;
    }
    return 0;
  },
  baseAvailable() {
    const token = Tokens.findOne(Session.get('baseCurrency'));
    if (token) {
      return token.balance;
    }
    return 0;
  },
  quoteAllowance() {
    const token = Tokens.findOne(Session.get('quoteCurrency'));
    if (token) {
      return token.allowance;
    }
    return 0;
  },
  baseAllowance() {
    const token = Tokens.findOne(Session.get('baseCurrency'));
    if (token) {
      return token.allowance;
    }
    return 0;
  },
  sup(event) {
  },

  hasAllowance(currency) {
    try {
      const token = Tokens.findOne(currency);
      const allowance = new BigNumber(token.allowance);
      return token && allowance.gte(web3Obj.toWei(new BigNumber(this.type() === 'sell' ? this.amount() : this.total())));
    } catch (e) {
      return false;
    }
  },
  canSubmit() {
    try {
      const type = this.type();
      const price = new BigNumber(this.price());
      const amount = new BigNumber(this.amount());
      const maxAmount = new BigNumber(this.maxAmount());
      const total = new BigNumber(this.total());
      const maxTotal = new BigNumber(this.maxTotal());
      const marketOpen = Session.get('market_open');
      const validTokenPair = Session.get('quoteCurrency') !== Session.get('baseCurrency');
      return marketOpen && price.gt(0) && amount.gt(0) && total.gt(0) && validTokenPair &&
        (type !== 'buy' || total.lte(maxTotal)) && (type !== 'sell' || amount.lte(maxAmount));
    } catch (e) {
      return false;
    }
  },
  preventDefault(event) {
    event.preventDefault();
  },
  betterOffer() {
    try {
      const quoteCurrency = Session.get('quoteCurrency');
      const baseCurrency = Session.get('baseCurrency');
      const price = new BigNumber(this.price());
      if (price.lte(0) || price.isNaN()) {
        this.bestOffer(undefined);
        return undefined;
      }

      let offer;
      if (this.type() === 'buy') {
        offer = Offers.findOne({ buyWhichToken: quoteCurrency, sellWhichToken: baseCurrency },
          { sort: { ask_price_sort: 1 } });
        if (offer && Object.prototype.hasOwnProperty.call(offer, 'ask_price')
          && price.gt(new BigNumber(offer.ask_price))) {
          this.bestOffer(offer._id);
          return offer;
        }
        this.bestOffer(undefined);
        return undefined;
      } else if (this.type() === 'sell') {
        offer = Offers.findOne({ buyWhichToken: baseCurrency, sellWhichToken: quoteCurrency },
          { sort: { ask_price_sort: 1 } });
        if (offer && Object.prototype.hasOwnProperty.call(offer, 'bid_price')
          && price.lt(new BigNumber(offer.bid_price))) {
          this.bestOffer(offer._id);
          return offer;
        }
      }
    } catch (e) {
      this.bestOffer(undefined);
      return undefined;
    }
    this.bestOffer(undefined);
    return undefined;
  },
  autofill(event) {
    event.preventDefault();
    const marketOpen = Session.get('market_open');
    let available = 0;
    if (!marketOpen) return false;
    if (this.type() === 'buy') {
      available = web3Obj.fromWei(this.quoteAvailable()).toString(10);
      this.total(available);
      this.calcAmount();
    } else if (this.type() === 'sell') {
      available = web3Obj.fromWei(this.baseAvailable()).toString(10);
      this.amount(available);
      this.calcTotal();
    }
    return false;
  },
  openOfferModal() {
    Session.set('selectedOffer', this.bestOffer());
  },
  showConfirmation(event) {
    event.preventDefault();
    this.offerPrice(this.price());
    this.offerAmount(this.amount());
    this.offerTotal(this.total());
    this.offerType(this.type());
  },
  showDepositTab() {
    $('#wrap').tab('show');
  },
  showAllowanceModal(token) {
    $(`#allowanceModal${token}`).modal('show');
  },
});
<|endoftext|>


<commit_before>import { find } from 'lodash';

export const parseEntityFunc = (host) => {
  const { network_interfaces: networkInterfaces } = host;
  const networkInterface = networkInterfaces && find(networkInterfaces, { id: host.primary_ip_id });
  const clockSpeed = host.cpu_brand.split('@ ')[1] || host.cpu_brand.split('@')[1];
  const clockSpeedFlt = parseFloat(clockSpeed.split('GHz')[0].trim());

  const additionalAttrs = {
    host_cpu: `${host.cpu_physical_cores} x ${Math.floor(clockSpeedFlt * 10) / 10} GHz`,
    target_type: 'hosts',
  };

  if (networkInterface) {
    additionalAttrs.host_ip_address = networkInterface.address;
    additionalAttrs.host_mac = networkInterface.mac;
  }

  return {
    ...host,
    ...additionalAttrs,
  };
};

export default { parseEntityFunc };
<commit_msg>Fix busted delete host modal (#1359)<commit_after>import { find } from 'lodash';

export const parseEntityFunc = (host) => {
  const { network_interfaces: networkInterfaces } = host;
  const networkInterface = networkInterfaces && find(networkInterfaces, { id: host.primary_ip_id });

  let clockSpeed = null;
  let clockSpeedFlt = null;
  let hostCpuOutput = null;

  if (host && host.cpu_brand) {
    clockSpeed = host.cpu_brand.split('@ ')[1] || host.cpu_brand.split('@')[1];
    clockSpeedFlt = parseFloat(clockSpeed.split('GHz')[0].trim());
    hostCpuOutput = `${host.cpu_physical_cores} x ${Math.floor(clockSpeedFlt * 10) / 10} GHz`;
  }

  const additionalAttrs = {
    host_cpu: hostCpuOutput,
    target_type: 'hosts',
  };

  if (networkInterface) {
    additionalAttrs.host_ip_address = networkInterface.address;
    additionalAttrs.host_mac = networkInterface.mac;
  }

  return {
    ...host,
    ...additionalAttrs,
  };
};

export default { parseEntityFunc };
<|endoftext|>


<commit_before>'use strict';

const debug = require('debug')('calendar:kollavarsham');
const sPrintF = require('sprintf-js').sprintf;
const _ = require('lodash');
const { Kollavarsham } = require('kollavarsham');
const kollavarsham = new Kollavarsham();

const separator = '---------------------------------------------------------------------------------------------------------';

const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];

const mlMonthNames = ['ജനുവരി', 'ഫെബ്രുവരി', 'മാർച്ച്', 'ഏപ്രിൽ ', 'മെയ്', 'ജൂണ്', 'ജൂലൈ', 'ഓഗസ്റ്റ്', 'സെപ്റ്റംബർ', 'ഒക്ടോബർ', 'നവംബർ', 'ഡിസംബർ']; // jshint ignore:line

const getCalendarDay = function (kollavarshamDate, useMalayalam) {
  const gregorianDate = kollavarshamDate.gregorianDate;
  const day = {
    year           : gregorianDate.getFullYear(),
    month          : monthNames[gregorianDate.getMonth()],
    mlMonth        : mlMonthNames[gregorianDate.getMonth()],
    date           : gregorianDate.getDate(),
    weekdayName    : kollavarshamDate.weekdayName,
    malayalamYear  : kollavarshamDate.year,
    malayalamMonth : kollavarshamDate.masaName,
    malayalamDay   : kollavarshamDate.date,
    naksatra       : kollavarshamDate.naksatraName,
    paksa          : kollavarshamDate.sakaDate.paksa,
    tithi          : kollavarshamDate.sakaDate.tithi
  };
  if (useMalayalam) {
    day.weekdayName = kollavarshamDate.mlWeekdayName;
    day.malayalamMonth = kollavarshamDate.mlMasaName;
    day.naksatra = kollavarshamDate.mlNaksatraName;
  }
  return day;
};

const mergeJSON = function (calendarDay, output) {
  const month = calendarDay.month;
  if (!output.months) {
    output.months = [];
  }
  if (_.result(_.find(output.months, {'name' : month}), 'name', 'default') === 'default') {
    output.months.push({
      'name' : month,
      'days' : [calendarDay]
    });
  } else {
    _.find(output.months, {'name' : month}).days.push(calendarDay);
  }
  return output;
};

const mergeText = function (calendarDay) {
  const result = sPrintF('| %4s  %-9s (%s)  %2s  %-10s | %4s  %10s  %2s | %-14s | %-10s | %2s |',
    calendarDay.year, calendarDay.month, calendarDay.mlMonth, calendarDay.date, calendarDay.weekdayName,
    calendarDay.malayalamYear, calendarDay.malayalamMonth, calendarDay.malayalamDay, calendarDay.naksatra,
    calendarDay.paksa, calendarDay.tithi);
  return result + '\n' + separator;
};

const convertGregorianDays = function (startDate, jsonOutput, untilDateCallback, lang) {
  let textOutput = separator + '\n';

  while (untilDateCallback(startDate)) {
    const calendarDay = getCalendarDay(kollavarsham.fromGregorianDate(startDate), lang === 'ml');
    jsonOutput = mergeJSON(calendarDay, jsonOutput);
    textOutput += mergeText(calendarDay) + '\n';
    startDate.setUTCDate(startDate.getUTCDate() + 1);
  }

  return {text : textOutput, json : jsonOutput};
};

module.exports = {
  getYear  : function (year, lang) {
    lang = lang || 'en';
    debug('Within getYear with year: %d', year);

    const initialJSONOutput = {year : year};

    const untilYearMatches = function (currentDate) {
      return currentDate.getFullYear() === year;
    };

    return convertGregorianDays(new Date(year, 0, 1), initialJSONOutput, untilYearMatches, lang);
  },
  getMonth : function (year, month, lang) {
    lang = lang || 'en';
    debug('Within getMonth with year: %d and month: %d', year, month);

    const initialJSONOutput = {
      year   : year,
      months : [{'name' : monthNames[month], 'days' : []}]
    };

    const untilYearAndMonthMatch = function (currentDate) {
      return currentDate.getFullYear() === year && currentDate.getMonth() === month;
    };

    return convertGregorianDays(new Date(year, month, 1), initialJSONOutput, untilYearAndMonthMatch, lang);
  },
  getDay   : function (year, month, day, lang) {
    lang = lang || 'en';
    debug('Within getDay with year: %d, month: %d and day: %d', year, month, day);

    const initialJSONOutput = {
      year   : year,
      months : [{'name' : monthNames[month], 'days' : []}]
    };

    const untilYearMonthAndDayMatch = function (currentDate) {
      return currentDate.getFullYear() === year && currentDate.getMonth() === month && currentDate.getDate() === day;
    };

    return convertGregorianDays(new Date(year, month, day), initialJSONOutput, untilYearMonthAndDayMatch, lang);
  }
};<commit_msg>Use arrow functions instead of `function` keyword<commit_after>'use strict';

const debug = require('debug')('calendar:kollavarsham');
const sPrintF = require('sprintf-js').sprintf;
const _ = require('lodash');
const { Kollavarsham } = require('kollavarsham');
const kollavarsham = new Kollavarsham();

const separator = '---------------------------------------------------------------------------------------------------------';

const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];

const mlMonthNames = ['ജനുവരി', 'ഫെബ്രുവരി', 'മാർച്ച്', 'ഏപ്രിൽ ', 'മെയ്', 'ജൂണ്', 'ജൂലൈ', 'ഓഗസ്റ്റ്', 'സെപ്റ്റംബർ', 'ഒക്ടോബർ', 'നവംബർ', 'ഡിസംബർ']; // jshint ignore:line

const getCalendarDay = (kollavarshamDate, useMalayalam) => {
  const gregorianDate = kollavarshamDate.gregorianDate;
  const day = {
    year           : gregorianDate.getFullYear(),
    month          : monthNames[gregorianDate.getMonth()],
    mlMonth        : mlMonthNames[gregorianDate.getMonth()],
    date           : gregorianDate.getDate(),
    weekdayName    : kollavarshamDate.weekdayName,
    malayalamYear  : kollavarshamDate.year,
    malayalamMonth : kollavarshamDate.masaName,
    malayalamDay   : kollavarshamDate.date,
    naksatra       : kollavarshamDate.naksatraName,
    paksa          : kollavarshamDate.sakaDate.paksa,
    tithi          : kollavarshamDate.sakaDate.tithi
  };
  if (useMalayalam) {
    day.weekdayName = kollavarshamDate.mlWeekdayName;
    day.malayalamMonth = kollavarshamDate.mlMasaName;
    day.naksatra = kollavarshamDate.mlNaksatraName;
  }
  return day;
};

const mergeJSON = (calendarDay, output) => {
  const month = calendarDay.month;
  if (!output.months) {
    output.months = [];
  }
  if (_.result(_.find(output.months, {'name' : month}), 'name', 'default') === 'default') {
    output.months.push({
      'name' : month,
      'days' : [calendarDay]
    });
  } else {
    _.find(output.months, {'name' : month}).days.push(calendarDay);
  }
  return output;
};

const mergeText = (calendarDay) => {
  const result = sPrintF('| %4s  %-9s (%s)  %2s  %-10s | %4s  %10s  %2s | %-14s | %-10s | %2s |',
    calendarDay.year, calendarDay.month, calendarDay.mlMonth, calendarDay.date, calendarDay.weekdayName,
    calendarDay.malayalamYear, calendarDay.malayalamMonth, calendarDay.malayalamDay, calendarDay.naksatra,
    calendarDay.paksa, calendarDay.tithi);
  return result + '\n' + separator;
};

const convertGregorianDays = (startDate, jsonOutput, untilDateCallback, lang) => {
  let textOutput = separator + '\n';

  while (untilDateCallback(startDate)) {
    const calendarDay = getCalendarDay(kollavarsham.fromGregorianDate(startDate), lang === 'ml');
    jsonOutput = mergeJSON(calendarDay, jsonOutput);
    textOutput += mergeText(calendarDay) + '\n';
    startDate.setUTCDate(startDate.getUTCDate() + 1);
  }

  return {text : textOutput, json : jsonOutput};
};

module.exports = {
  getYear  : (year, lang) => {
    lang = lang || 'en';
    debug('Within getYear with year: %d', year);

    const initialJSONOutput = {year : year};

    const untilYearMatches = (currentDate) => {
      return currentDate.getFullYear() === year;
    };

    return convertGregorianDays(new Date(year, 0, 1), initialJSONOutput, untilYearMatches, lang);
  },
  getMonth : (year, month, lang) => {
    lang = lang || 'en';
    debug('Within getMonth with year: %d and month: %d', year, month);

    const initialJSONOutput = {
      year   : year,
      months : [{'name' : monthNames[month], 'days' : []}]
    };

    const untilYearAndMonthMatch = (currentDate) => {
      return currentDate.getFullYear() === year && currentDate.getMonth() === month;
    };

    return convertGregorianDays(new Date(year, month, 1), initialJSONOutput, untilYearAndMonthMatch, lang);
  },
  getDay   : (year, month, day, lang) => {
    lang = lang || 'en';
    debug('Within getDay with year: %d, month: %d and day: %d', year, month, day);

    const initialJSONOutput = {
      year   : year,
      months : [{'name' : monthNames[month], 'days' : []}]
    };

    const untilYearMonthAndDayMatch = (currentDate) => {
      return currentDate.getFullYear() === year && currentDate.getMonth() === month && currentDate.getDate() === day;
    };

    return convertGregorianDays(new Date(year, month, day), initialJSONOutput, untilYearMonthAndDayMatch, lang);
  }
};
<|endoftext|>


<commit_before>var Spec = require('../models/Spec');
var Problem = require('../models/Problem');
var isSpec = require('../utils/isSpec');
var isObj = require('../preds/isObj');
var core = require('./core');
var cat = core.cat;
var or = core.or;
var fspec = require('./fspec');

var SPEC_TYPE_KEYS = 'KEYS';

function keys(params) {
  var reqSpecs = params.req;
  var optSpecs = params.opt;
  return new Spec(SPEC_TYPE_KEYS, params, _genKeyConformer(reqSpecs), null);
};

function _genKeyConformer(reqSpecs, optSpec) {
  return function tryConformKeys(x) {
    var reqProblems = reqSpecs.filter(function doesNotHaveKey(r) { return x[r] === undefined; });
    if(reqProblems.length > 0) {
      return new Problem(x, reqProblems, 'req: keys required: ' + reqProblems.join(', ') );
    } else {
      return x;
    }
  }
}

var TYPE_PROPS = 'PROPS';

var KeyDefs = isObj; //TODO
var FieldDefs = isObj;

var PropsSpec = fspec({
  args: cat(
    'keyDefs', KeyDefs,
    'fieldDefs', core.zeroOrOne(FieldDefs)
  ),
  ret: isSpec,
});

function props(cargs) {
  //TODO
  var keyDefs = cargs.keyDefs;
  var req = keyDefs.req;
  var opt = keyDefs.opt;

  return new Spec(TYPE_PROPS, cargs, _genPropsConformer(req), null);
}

function _genPropsConformer(req, opt) {
  var keyConformer;
  return function tryConformProps(x) {
    if (!keyConformer) {
      keyConformer = _genKeyConformer(req, opt); //lazy
    }
    var keyResult = keyConformer(x);
    //TODO
    return keyResult;
  }
}

module.exports = {
  keys: keys,
  props: PropsSpec.wrapConformedArgs(props),
};
<commit_msg>objRelated wip<commit_after>var Spec = require('../models/Spec');
var oAssign = require('object-assign');
var Problem = require('../models/Problem');
var isSpec = require('../utils/isSpec');
var isObj = require('../preds/isObj');
var isStr = require('../preds/isStr');
var isExpr = require('../utils/isExpr');
var core = require('./core');
var cat = core.cat;
var or = core.or;
var fspec = require('./fspec');

var SPEC_TYPE_KEYS = 'KEYS';

function keys(params) {
  var reqSpecs = params.req;
  var optSpecs = params.opt;
  return new Spec(SPEC_TYPE_KEYS, params, _genKeyConformer(reqSpecs), null);
};

function _genKeyConformer(reqSpecs, optSpec) {
  return function tryConformKeys(x) {
    if(reqSpecs) {
      var reqProblems = [];
      for(var k in reqSpecs) {
        if(x[k] === undefined) {
          reqProblems.push(x[k]);
        }
      }
      if(reqProblems.length > 0) {
        return new Problem(x, reqProblems, 'req: keys required: ' + reqProblems.join(', ') );
      }
    }
    return x;
  }
}

var TYPE_PROPS = 'PROPS';

var FieldDefs = propsOp({
  opt: {
    'fields':
    {
      keySpec: isExpr,
      valSpec: or(
        'expr', isExpr,
        'keyValExprPair', cat('keyExpr', isExpr, 'valExpr', isExpr)
      ),
    },
  },
});

var PropArgs = propsOp({
  opt: {
    req: { valSpec: FieldDefs },
    opt: { valSpec: FieldDefs },
  },
});

var PropsSpec = fspec({
  args: cat(FieldDefs),
  ret: isSpec,
});

function propsOp(cargs) {
  //TODO
  var req = cargs.req;
  var opt = cargs.opt;

  return new Spec(TYPE_PROPS, cargs, _genPropsConformer(req, opt), null);
}

function parseFieldDef(x, d, defs) {

}

function _genPropsConformer(reqSpecs, optSpecs) {
  var keyConformer;
  return function tryConformProps(x) {

    var r = {};

    if(reqSpecs) {
      for (var i = 0; i < reqSpecs.length; i++) {
        var defs = reqSpecs[i];
        r[defs.name] = parseFieldDef(x, defs);
      }
    }

    if(optSpecs) {
      r = oAssign(r, parseFieldDef());
    }

    if (!keyConformer) {
      keyConformer = _genKeyConformer(reqSpecs, optSpecs); //lazy
    }
    var keyResult = keyConformer(x);
    //TODO
    return keyResult;
  }
}

module.exports = {
  keys: keys,
  props: PropsSpec.wrapConformedArgs(propsOp),
};
<|endoftext|>


<commit_before>//
// Select all links with the 'rel' attribute set to "less"
//
var sheets = document.querySelectorAll("link[rel=less]");

for (var i = 0; i < sheets.length; i++) {
    xhr(sheets[i].href, function (data) {
        new(less.Parser)({ optimization: 3 }).parse(data, function (e, root) {
            document.styleSheets[0].insertRule(root.toCSS());
        });
    });
}

function xhr(url, callback, errback) {
    var xhr = new(XMLHttpRequest);
    var headers = {
        'X-Requested-With': 'XMLHttpRequest',
        'Accept': 'text/*'
    };

    xhr.open('get', url, true);
    xhr.onreadystatechange = function () {
        if (this.readyState != 4) { return }

        if (this.status >= 200 && this.status < 300) {
            callback(this.responseText);
        } else if (typeof(errback) === 'function') {
            errback(this.responseText);
        }
    };
    xhr.send();
}
<commit_msg>functional browser based parsing + css node creation<commit_after>//
// Select all links with the 'rel' attribute set to "less"
//
var sheets = document.querySelectorAll("link[rel=less]");

for (var i = 0; i < sheets.length; i++) {
    (function (sheet) { // Because the functions here are async, we need to create a closure
        xhr(sheet.href, function (data) {
            new(less.Parser)({ optimization: 3 }).parse(data, function (e, root) {
                var css = document.createElement('style');
                css.type = 'text/css';
                css.media = 'screen';
                css.title = sheet.title || sheet.href.match(/\/([-\w]+)\.[a-z]+$/i)[1];
                css.innerHTML = root.toCSS();
                document.getElementsByTagName('head')[0].appendChild(css);
            });
        });
    })(sheets[i]);
}

function xhr(url, callback, errback) {
    var xhr = new(XMLHttpRequest);
    var headers = {
        'X-Requested-With': 'XMLHttpRequest',
        'Accept': 'text/*'
    };

    xhr.open('get', url, true);
    xhr.onreadystatechange = function () {
        if (this.readyState != 4) { return }

        if (this.status >= 200 && this.status < 300) {
            callback(this.responseText);
        } else if (typeof(errback) === 'function') {
            errback(this.responseText);
        }
    };
    xhr.send();
}
<|endoftext|>


<commit_before>import { app, ipcMain, BrowserWindow, dialog } from 'electron';
import { autoUpdater } from 'electron-updater';
import jetpack from 'fs-jetpack';

const userDataDir = jetpack.cwd(app.getPath('userData'));
const updateStoreFile = 'update.json';
let checkForUpdatesEvent;

autoUpdater.autoDownload = false;

let updateFile = {};
try {
    updateFile = userDataDir.read(updateStoreFile, 'json') || {};
} catch (err) {
    console.log(err);
}

function updateDownloaded () {
    dialog.showMessageBox({
        title: 'Update Ready to Install',
        message: 'Update has been downloaded',
        buttons: [
            'Install Later',
            'Install Now'
        ],
        defaultId: 1
    }, (response) => {
        if (response === 0) {
            dialog.showMessageBox({
                title: 'Installing Later',
                message: 'Update will be installed when you exit the app'
            });
        } else {
            autoUpdater.quitAndInstall();
        }
    });
}

function updateNotAvailable () {
    if (checkForUpdatesEvent) {
        checkForUpdatesEvent.sender.send('update-result', false);
        checkForUpdatesEvent = null;
    }
}

function updateAvailable ({version}) {
    if (checkForUpdatesEvent) {
        checkForUpdatesEvent.sender.send('update-result', true);
        checkForUpdatesEvent = null;
    } else if (updateFile.skip === version) {
        console.log(`Skipping version: ${version}`);
        return;
    }

    let window = new BrowserWindow({
        title: 'Update Available',
        width: 600,
        height: 330,
        show : false,
        center: true,
        resizable: false,
        maximizable: false,
        minimizable: false
    });

    window.loadURL(`file://${__dirname}/public/update.html`);
    window.setMenuBarVisibility(false);

    window.webContents.on('did-finish-load', () => {
        window.webContents.send('new-version', version);
        window.show();
    });

    ipcMain.once('update-response', (e, type) => {
        switch (type) {
            case 'skip':
                updateFile.skip = version;
                userDataDir.write(updateStoreFile, updateFile, { atomic: true });
                dialog.showMessageBox({
                    title: 'Skip Update',
                    message: 'We will notify you when the next update is available\n' +
                        'If you change your mind you can check for updates from the About menu.'
                }, () => window.close());
                break;
            case 'remind':
                dialog.showMessageBox({
                    title: 'Remind Later',
                    message: 'We will remind you next time you start the app'
                }, () => window.close());
                break;
            case 'update':
                dialog.showMessageBox({
                    title: 'Downloading Update',
                    message: 'You will be notified when the update is ready to be installed'
                }, () => window.close());
                autoUpdater.downloadUpdate();
                break;
        }
    });

    window.on('closed', () => {
        window = null;
        ipcMain.removeAllListeners('update-response');
    });
}

function checkForUpdates () {
    autoUpdater.on('update-available', updateAvailable);
    autoUpdater.on('update-not-available', updateNotAvailable);

    autoUpdater.on('download-progress', ({percent}) => {
        console.log(`Update progress: ${percent}`);
    });

    autoUpdater.on('update-downloaded', updateDownloaded);

    // Event from about window
    ipcMain.on('check-for-updates', (e, autoUpdate) => {
        if (autoUpdate === true || autoUpdate === false) {
            updateFile.autoUpdate = autoUpdate;
            userDataDir.write(updateStoreFile, updateFile, { atomic: true });
        } else if (autoUpdate === 'auto') {
            e.returnValue = updateFile.autoUpdate;
        } else {
            checkForUpdatesEvent = e;
            autoUpdater.checkForUpdates();
        }
    });

    if (updateFile.autoUpdate !== false) {
        autoUpdater.checkForUpdates();
    }
}

export {
    checkForUpdates
};
<commit_msg>Make sure app quits on mac on update<commit_after>import { app, ipcMain, BrowserWindow, dialog } from 'electron';
import { autoUpdater } from 'electron-updater';
import jetpack from 'fs-jetpack';

const userDataDir = jetpack.cwd(app.getPath('userData'));
const updateStoreFile = 'update.json';
let checkForUpdatesEvent;

autoUpdater.autoDownload = false;

let updateFile = {};
try {
    updateFile = userDataDir.read(updateStoreFile, 'json') || {};
} catch (err) {
    console.log(err);
}

function updateDownloaded () {
    dialog.showMessageBox({
        title: 'Update Ready to Install',
        message: 'Update has been downloaded',
        buttons: [
            'Install Later',
            'Install Now'
        ],
        defaultId: 1
    }, (response) => {
        if (response === 0) {
            dialog.showMessageBox({
                title: 'Installing Later',
                message: 'Update will be installed when you exit the app'
            });
        } else {
            autoUpdater.quitAndInstall();
            setTimeout(() => app.quit(), 1000);
        }
    });
}

function updateNotAvailable () {
    if (checkForUpdatesEvent) {
        checkForUpdatesEvent.sender.send('update-result', false);
        checkForUpdatesEvent = null;
    }
}

function updateAvailable ({version}) {
    if (checkForUpdatesEvent) {
        checkForUpdatesEvent.sender.send('update-result', true);
        checkForUpdatesEvent = null;
    } else if (updateFile.skip === version) {
        console.log(`Skipping version: ${version}`);
        return;
    }

    let window = new BrowserWindow({
        title: 'Update Available',
        width: 600,
        height: 330,
        show : false,
        center: true,
        resizable: false,
        maximizable: false,
        minimizable: false
    });

    window.loadURL(`file://${__dirname}/public/update.html`);
    window.setMenuBarVisibility(false);

    window.webContents.on('did-finish-load', () => {
        window.webContents.send('new-version', version);
        window.show();
    });

    ipcMain.once('update-response', (e, type) => {
        switch (type) {
            case 'skip':
                updateFile.skip = version;
                userDataDir.write(updateStoreFile, updateFile, { atomic: true });
                dialog.showMessageBox({
                    title: 'Skip Update',
                    message: 'We will notify you when the next update is available\n' +
                        'If you change your mind you can check for updates from the About menu.'
                }, () => window.close());
                break;
            case 'remind':
                dialog.showMessageBox({
                    title: 'Remind Later',
                    message: 'We will remind you next time you start the app'
                }, () => window.close());
                break;
            case 'update':
                dialog.showMessageBox({
                    title: 'Downloading Update',
                    message: 'You will be notified when the update is ready to be installed'
                }, () => window.close());
                autoUpdater.downloadUpdate();
                break;
        }
    });

    window.on('closed', () => {
        window = null;
        ipcMain.removeAllListeners('update-response');
    });
}

function checkForUpdates () {
    autoUpdater.on('update-available', updateAvailable);
    autoUpdater.on('update-not-available', updateNotAvailable);

    autoUpdater.on('download-progress', ({percent}) => {
        console.log(`Update progress: ${percent}`);
    });

    autoUpdater.on('update-downloaded', updateDownloaded);

    // Event from about window
    ipcMain.on('check-for-updates', (e, autoUpdate) => {
        if (autoUpdate === true || autoUpdate === false) {
            updateFile.autoUpdate = autoUpdate;
            userDataDir.write(updateStoreFile, updateFile, { atomic: true });
        } else if (autoUpdate === 'auto') {
            e.returnValue = updateFile.autoUpdate;
        } else {
            checkForUpdatesEvent = e;
            autoUpdater.checkForUpdates();
        }
    });

    if (updateFile.autoUpdate !== false) {
        autoUpdater.checkForUpdates();
    }
}

export {
    checkForUpdates
};
<|endoftext|>


<commit_before>const co = require('co');
const ga = require('../common/ga.js');
const constants = require('../common/constants.js');
const bg = require('../common/background.js');
const common = require('../common/common.js');

const getSessionKey = entityId => `session_${entityId}`;
const getCampaignDataKey = entityId => `campaignData_${entityId}`;
const getEntityIdFromSession = session => session.replace('session_', '');

const alarmPeriodMinutes = 240;

function checkEntityId(entityId) {
    if (!(entityId && entityId != 'undefined' && entityId != 'null')) {
        throw new Error(`invalid entityId={${entityId}}`);
    }
}

bg.messageListener(function*(req) {
    if (req.action == 'setSession')
        return yield setSession(req);
    else if (req.action == 'getUser')
        return yield bg.getUser();
    else if (req.action == 'getAllowedCampaigns')
        return yield getAllowedCampaigns(req.entityId);
    else if (req.action == 'getCampaignSummaries')
        return yield getCampaignSummaries(req.entityId);
    else if (req.action == 'getDataHistory')
        return yield getDataHistory(req.entityId, req.campaignId);
    else if (req.action == 'getAggregateCampaignHistory')
        return yield getAggregateCampaignHistory(req.entityId, req.campaignIds);
    else if (req.action == 'getKeywordData')
        return yield getKeywordData(req.entityId, req.adGroupId);
    else if (req.action == 'getAggregateKeywordData')
        return yield getAggregateKeywordData(req.entityId, req.adGroupIds);
    else if (req.action == 'setCampaignMetadata')
        return yield setCampaignMetadata(req.entityId, req.campaignId, req.asin);
    else if (req.action == 'setAdGroupMetadata')
        return yield setAdGroupMetadata(req.entityId, req.adGroupId, req.campaignId);
    else if (req.action == 'updateKeyword')
        return yield updateKeyword(req.entityId, req.keywordIdList, req.operation, req.dataValues);
    throw new Error('unknown action');
});

chrome.alarms.onAlarm.addListener((session) => {
    let entityId = getEntityIdFromSession(session.name);
    try {
        checkEntityId(entityId);
    }
    catch (ex) {
        chrome.alarms.clear(session.name, cleared => console.log("cleared useless alarm", cleared));
        return;
    }

    co(function*() {
        yield* alarmHandler(entityId);
    });
});

function* pageArray(array, step) {
    for (let index = 0; index < array.length; index += step) {
        yield array.slice(index, index + step);
    }
}

function* alarmHandler(entityId) {
    console.log('Alarm handler start at', new Date());

    // This will throw if we can't get through, effectively calling the whole
    // thing off.
    yield* requestCampaignData(entityId);

    const adGroups = yield* getAdGroups(entityId);
    for (const item of adGroups) {
        yield* requestKeywordData(entityId, item.adGroupId);
    }

    console.log('Alarm handler finish at', new Date());
}

const setSession = bg.coMemo(function*(req) {
    console.log('page session startup for', req);
    let sessionKey = getSessionKey(req.entityId);

    yield ga.mpromise(resolve => {
        chrome.alarms.get(sessionKey, alarm => {
            if (!alarm) {
                chrome.alarms.create(sessionKey, {
                    when: Date.now() + 500,
                    periodInMinutes: alarmPeriodMinutes,
                });
                console.log('set alarm for', sessionKey);
                resolve(true);
            }
            resolve(false);
        });
    });

    // Request data on login if it's stale
    let lastCampaignData = localStorage.getItem(getCampaignDataKey(req.entityId));
    if (!lastCampaignData || Date.now() - lastCampaignData >= constants.timespan.minute * alarmPeriodMinutes) {
        yield* alarmHandler(req.entityId);
    }
});

const getAllowedCampaigns = bg.coMemo(function*(entityId) {
    checkEntityId(entityId);
    const allowed = yield bg.ajax(`${bg.serviceUrl}/api/data/${entityId}/allowed`, {
        method: 'GET',
        dataType: 'json'
    });

    if (allowed.length)
        return allowed;

    // nb: setSession is memoized so this should never actually rerun, it just
    // makes us wait until setSession is done
    yield setSession({ entityId });

    return yield bg.ajax(`${bg.serviceUrl}/api/data/${entityId}/allowed`, {
        method: 'GET',
        dataType: 'json'
    });
}, { maxAge: 2 * constants.timespan.minute });

const getCampaignSummaries = bg.coMemo(function*(entityId) {
    checkEntityId(entityId);
    return yield bg.ajax(`${bg.serviceUrl}/api/data/${entityId}/summary`, {
        method: 'GET',
        dataType: 'json'
    });
}, { maxAge: 2 * constants.timespan.minute });

function* requestCampaignData(entityId) {
    checkEntityId(entityId);
    const lastRequestSucceeded = JSON.parse(localStorage.getItem('lastRequestSucceeded'));

    let timestamp = Date.now();
    let data = null;
    try {
        console.log('requesting campaign data for', entityId);
        data = yield bg.ajax('https://ams.amazon.com/api/rta/campaigns', {
            method: 'GET',
            data: {
                entityId,
                /* TODO: use these once Amazon actually supports them
                status: null,
                startDate: null,
                endDate: null,
                */
            },
            dataType: 'json',
        });
    }
    catch (ex) {
        if (bg.handleAuthErrors(ex) && lastRequestSucceeded) {
            localStorage.setItem('lastRequestSucceeded', false);
            notifyNeedCredentials(entityId);
            return null;
        }
        throw ex;
    }
    localStorage.setItem('lastRequestSucceeded', true);
    yield* storeDataCloud(entityId, timestamp, data);

    if (data && data.aaData && data.aaData.length) {
        let campaignIds = data.aaData.map(x => x.campaignId);
        yield* requestCampaignStatus(entityId, campaignIds, timestamp);
    }

    localStorage.setItem(getCampaignDataKey(entityId), timestamp);
    return data;
}

function* requestCampaignStatus(entityId, campaignIds, timestamp) {
    checkEntityId(entityId);

    // Chop the campaignId list into bite-sized chunks
    for (const chunk of pageArray(campaignIds, 20)) {
        const data = yield bg.ajax('https://ams.amazon.com/api/rta/campaign-status', {
            method: 'GET',
            data: {
                entityId,
                campaignIds: chunk.join(','),
            },
            dataType: 'json',
        });

        yield* storeStatusCloud(entityId, timestamp, data);
    }
}

function* requestKeywordData(entityId, adGroupId) {
    checkEntityId(entityId);

    let timestamp = Date.now();
    console.log('requesting keyword data for', entityId, adGroupId);
    const data = yield bg.ajax('https://ams.amazon.com/api/sponsored-products/getAdGroupKeywordList', {
        method: 'POST',
        data: {
            entityId, adGroupId,
            /* TODO: use these once Amazon actually supports them
            status: null,
            startDate: null,
            endDate: null,
            */
        },
        dataType: 'json',
    });

    if (data.message) {
        ga.mga('event', 'error-handled', 'keyword-data-failure', `${adGroupId}: ${data.message}`);
        return;
    }

    yield* storeKeywordDataCloud(entityId, adGroupId, timestamp, data);
}

function* storeDataCloud(entityId, timestamp, data) {
    return yield bg.ajax(`${bg.serviceUrl}/api/data/${entityId}?timestamp=${timestamp}`, {
        method: 'PUT',
        data: JSON.stringify(data),
        contentType: 'application/json',
    });
}

function* storeStatusCloud(entityId, timestamp, data) {
    return yield bg.ajax(`${bg.serviceUrl}/api/campaignStatus/${entityId}?timestamp=${timestamp}`, {
        method: 'PUT',
        data: JSON.stringify(data),
        contentType: 'application/json',
    });
}

function* storeKeywordDataCloud(entityId, adGroupId, timestamp, data) {
    // Chop the large keyword list into small, bite-sized chunks for easier
    // digestion on the server.
    for (const chunk of pageArray(data.aaData, 20)) {
        yield bg.ajax(`${bg.serviceUrl}/api/keywordData/${entityId}/${adGroupId}?timestamp=${timestamp}`, {
            method: 'PUT',
            data: JSON.stringify({ aaData: chunk }),
            contentType: 'application/json',
        });
    }
}

const getDataHistory = bg.coMemo(function*(entityId, campaignId) { // TODO: date ranges, etc.
    checkEntityId(entityId);
    const snapshots = yield bg.ajax(`${bg.serviceUrl}/api/data/${entityId}/${campaignId}`, {
        method: 'GET',
        dataType: 'json'
    });
    return common.convertSnapshotsToDeltas(snapshots);
});

const getAggregateCampaignHistory = bg.coMemo(function*(entityId, campaignIds) {
    let aggregate = [];
    for (const page of pageArray(campaignIds, 6)) {
        const promises = [];
        for (const campaignId of page) {
            promises.push(co(function*() {
                let history = yield getDataHistory(entityId, campaignId);
                aggregate = aggregate.concat(...history);
            }));
        }
        yield Promise.all(promises);
    }

    return aggregate.sort((a, b) => a.timestamp - b.timestamp);
});

const getKeywordData = bg.coMemo(function*(entityId, adGroupId) {
    checkEntityId(entityId);
    const ajaxOptions = {
        url: `${bg.serviceUrl}/api/keywordData/${entityId}/${adGroupId}`,
        method: 'GET',
        dataType: 'json',
    };
    let data = yield bg.ajax(ajaxOptions);

    if (!data || data.length == 0) {
        // Possibly this is the first time we've ever seen this campaign. If so,
        // let's query Amazon and populate our own servers, and then come back.
        // This is very slow but should usually only happen once.
        yield* requestKeywordData(entityId, adGroupId);
        data = yield bg.ajax(ajaxOptions);
    }

    return data;
});

const getAggregateKeywordData = bg.coMemo(function*(entityId, adGroupIds) {
    let keywordSets = [];

    for (const page of pageArray(adGroupIds, 6)) {
        const kwSets = yield Promise.all(page.map(adGroupId => co(getKeywordData(entityId, adGroupId))));
        keywordSets = keywordSets.concat(kwSets);
    }

    return keywordSets;
});

const setCampaignMetadata = bg.coMemo(function*(entityId, campaignId, asin) {
    checkEntityId(entityId);
    return yield bg.ajax(`${bg.serviceUrl}/api/campaignMetadata/${entityId}/${campaignId}`, {
        method: 'PUT',
        data: JSON.stringify({ asin }),
        contentType: 'application/json',
    });
});

const setAdGroupMetadata = bg.coMemo(function*(entityId, adGroupId, campaignId) {
    checkEntityId(entityId);
    return yield bg.ajax(`${bg.serviceUrl}/api/adGroupMetadata/${entityId}/${adGroupId}`, {
        method: 'PUT',
        data: JSON.stringify({ campaignId }),
        contentType: 'application/json',
    });
});

function* getAdGroups(entityId) {
    checkEntityId(entityId);
    return yield bg.ajax(`${bg.serviceUrl}/api/adGroups/${entityId}`, {
        method: 'GET',
        dataType: 'json',
    });
}

function* updateKeyword(entityId, keywordIdList, operation, dataValues) {
    // TODO: the parameters to the Amazon API imply that you can pass more than
    // 1 keyword at a time, but testing this shows that doing so just generates
    // an error. So we do it the stupid way instead, with a loop.
    const timestamp = Date.now();

    const results = [];
    for (const chunk of pageArray(keywordIdList, 6)) {
        let requests = [];
        for (let id of chunk) {
            let postData = Object.assign({operation, entityId, keywordIds: id}, dataValues);
            requests.push(bg.ajax({
                url: 'https://ams.amazon.com/api/sponsored-products/updateKeywords/',
                method: 'POST',
                data: postData,
                dataType: 'json',
            }));
        }

        const responses = yield Promise.all(requests);
        const successes = chunk.filter((x, index) => responses[index].success);
        yield bg.ajax(`${bg.serviceUrl}/api/keywordData/${entityId}?timestamp=${timestamp}`, {
            method: 'PATCH',
            dataType: 'json',
            data: JSON.stringify({ operation, dataValues, keywordIds: successes }),
            contentType: 'application/json',
        });

        results.concat(...responses);
    }

    // TODO: in the case that we have a lot of these (bulk update), implement
    // progress feedback.
    return { success: results.every(x => x.success) };
}

let notificationExists = false;
function notifyNeedCredentials(entityId) {
    if (!notificationExists) {
        let notificationId = `machete-${entityId}-need-credentials`;
        chrome.notifications.create(notificationId, {
            type: "basic",
            iconUrl: "images/machete-128.png",
            title: "Sign in to AMS",
            message: "Machete needs you to sign in to AMS so it can keep your campaign history up-to-date.",
            contextMessage: "Click to sign in at https://ams.amazon.com/",
            isClickable: true,
            requireInteraction: true,
        });

        notificationExists = true;
        ga.mga('event', 'credential-popup', 'show');
        chrome.notifications.onClicked.addListener((clickId) => {
            if (clickId == notificationId) {
                ga.mga('event', 'credential-popup', 'click');
                chrome.tabs.create({ url: "https://ams.amazon.com/ads/dashboard" });
                chrome.notifications.clear(notificationId);
                notificationExists = false;
            }
        });
        chrome.notifications.onClosed.addListener(() => {
            notificationExists = false;
            ga.mga('event', 'credential-popup', 'dismiss');
        });
    }
}
<commit_msg>store keyword data in bigger chunks<commit_after>const co = require('co');
const ga = require('../common/ga.js');
const constants = require('../common/constants.js');
const bg = require('../common/background.js');
const common = require('../common/common.js');

const getSessionKey = entityId => `session_${entityId}`;
const getCampaignDataKey = entityId => `campaignData_${entityId}`;
const getEntityIdFromSession = session => session.replace('session_', '');

const alarmPeriodMinutes = 240;

function checkEntityId(entityId) {
    if (!(entityId && entityId != 'undefined' && entityId != 'null')) {
        throw new Error(`invalid entityId={${entityId}}`);
    }
}

bg.messageListener(function*(req) {
    if (req.action == 'setSession')
        return yield setSession(req);
    else if (req.action == 'getUser')
        return yield bg.getUser();
    else if (req.action == 'getAllowedCampaigns')
        return yield getAllowedCampaigns(req.entityId);
    else if (req.action == 'getCampaignSummaries')
        return yield getCampaignSummaries(req.entityId);
    else if (req.action == 'getDataHistory')
        return yield getDataHistory(req.entityId, req.campaignId);
    else if (req.action == 'getAggregateCampaignHistory')
        return yield getAggregateCampaignHistory(req.entityId, req.campaignIds);
    else if (req.action == 'getKeywordData')
        return yield getKeywordData(req.entityId, req.adGroupId);
    else if (req.action == 'getAggregateKeywordData')
        return yield getAggregateKeywordData(req.entityId, req.adGroupIds);
    else if (req.action == 'setCampaignMetadata')
        return yield setCampaignMetadata(req.entityId, req.campaignId, req.asin);
    else if (req.action == 'setAdGroupMetadata')
        return yield setAdGroupMetadata(req.entityId, req.adGroupId, req.campaignId);
    else if (req.action == 'updateKeyword')
        return yield updateKeyword(req.entityId, req.keywordIdList, req.operation, req.dataValues);
    throw new Error('unknown action');
});

chrome.alarms.onAlarm.addListener((session) => {
    let entityId = getEntityIdFromSession(session.name);
    try {
        checkEntityId(entityId);
    }
    catch (ex) {
        chrome.alarms.clear(session.name, cleared => console.log("cleared useless alarm", cleared));
        return;
    }

    co(function*() {
        yield* alarmHandler(entityId);
    });
});

function* pageArray(array, step) {
    for (let index = 0; index < array.length; index += step) {
        yield array.slice(index, index + step);
    }
}

function* alarmHandler(entityId) {
    console.log('Alarm handler start at', new Date());

    // This will throw if we can't get through, effectively calling the whole
    // thing off.
    yield* requestCampaignData(entityId);

    const adGroups = yield* getAdGroups(entityId);
    for (const item of adGroups) {
        yield* requestKeywordData(entityId, item.adGroupId);
    }

    console.log('Alarm handler finish at', new Date());
}

const setSession = bg.coMemo(function*(req) {
    console.log('page session startup for', req);
    let sessionKey = getSessionKey(req.entityId);

    yield ga.mpromise(resolve => {
        chrome.alarms.get(sessionKey, alarm => {
            if (!alarm) {
                chrome.alarms.create(sessionKey, {
                    when: Date.now() + 500,
                    periodInMinutes: alarmPeriodMinutes,
                });
                console.log('set alarm for', sessionKey);
                resolve(true);
            }
            resolve(false);
        });
    });

    // Request data on login if it's stale
    let lastCampaignData = localStorage.getItem(getCampaignDataKey(req.entityId));
    if (!lastCampaignData || Date.now() - lastCampaignData >= constants.timespan.minute * alarmPeriodMinutes) {
        yield* alarmHandler(req.entityId);
    }
});

const getAllowedCampaigns = bg.coMemo(function*(entityId) {
    checkEntityId(entityId);
    const allowed = yield bg.ajax(`${bg.serviceUrl}/api/data/${entityId}/allowed`, {
        method: 'GET',
        dataType: 'json'
    });

    if (allowed.length)
        return allowed;

    // nb: setSession is memoized so this should never actually rerun, it just
    // makes us wait until setSession is done
    yield setSession({ entityId });

    return yield bg.ajax(`${bg.serviceUrl}/api/data/${entityId}/allowed`, {
        method: 'GET',
        dataType: 'json'
    });
}, { maxAge: 2 * constants.timespan.minute });

const getCampaignSummaries = bg.coMemo(function*(entityId) {
    checkEntityId(entityId);
    return yield bg.ajax(`${bg.serviceUrl}/api/data/${entityId}/summary`, {
        method: 'GET',
        dataType: 'json'
    });
}, { maxAge: 2 * constants.timespan.minute });

function* requestCampaignData(entityId) {
    checkEntityId(entityId);
    const lastRequestSucceeded = JSON.parse(localStorage.getItem('lastRequestSucceeded'));

    let timestamp = Date.now();
    let data = null;
    try {
        console.log('requesting campaign data for', entityId);
        data = yield bg.ajax('https://ams.amazon.com/api/rta/campaigns', {
            method: 'GET',
            data: {
                entityId,
                /* TODO: use these once Amazon actually supports them
                status: null,
                startDate: null,
                endDate: null,
                */
            },
            dataType: 'json',
        });
    }
    catch (ex) {
        if (bg.handleAuthErrors(ex) && lastRequestSucceeded) {
            localStorage.setItem('lastRequestSucceeded', false);
            notifyNeedCredentials(entityId);
            return null;
        }
        throw ex;
    }
    localStorage.setItem('lastRequestSucceeded', true);
    yield* storeDataCloud(entityId, timestamp, data);

    if (data && data.aaData && data.aaData.length) {
        let campaignIds = data.aaData.map(x => x.campaignId);
        yield* requestCampaignStatus(entityId, campaignIds, timestamp);
    }

    localStorage.setItem(getCampaignDataKey(entityId), timestamp);
    return data;
}

function* requestCampaignStatus(entityId, campaignIds, timestamp) {
    checkEntityId(entityId);

    // Chop the campaignId list into bite-sized chunks
    for (const chunk of pageArray(campaignIds, 20)) {
        const data = yield bg.ajax('https://ams.amazon.com/api/rta/campaign-status', {
            method: 'GET',
            data: {
                entityId,
                campaignIds: chunk.join(','),
            },
            dataType: 'json',
        });

        yield* storeStatusCloud(entityId, timestamp, data);
    }
}

function* requestKeywordData(entityId, adGroupId) {
    checkEntityId(entityId);

    let timestamp = Date.now();
    console.log('requesting keyword data for', entityId, adGroupId);
    const data = yield bg.ajax('https://ams.amazon.com/api/sponsored-products/getAdGroupKeywordList', {
        method: 'POST',
        data: {
            entityId, adGroupId,
            /* TODO: use these once Amazon actually supports them
            status: null,
            startDate: null,
            endDate: null,
            */
        },
        dataType: 'json',
    });

    if (data.message) {
        ga.mga('event', 'error-handled', 'keyword-data-failure', `${adGroupId}: ${data.message}`);
        return;
    }

    yield* storeKeywordDataCloud(entityId, adGroupId, timestamp, data);
}

function* storeDataCloud(entityId, timestamp, data) {
    return yield bg.ajax(`${bg.serviceUrl}/api/data/${entityId}?timestamp=${timestamp}`, {
        method: 'PUT',
        data: JSON.stringify(data),
        contentType: 'application/json',
    });
}

function* storeStatusCloud(entityId, timestamp, data) {
    return yield bg.ajax(`${bg.serviceUrl}/api/campaignStatus/${entityId}?timestamp=${timestamp}`, {
        method: 'PUT',
        data: JSON.stringify(data),
        contentType: 'application/json',
    });
}

function* storeKeywordDataCloud(entityId, adGroupId, timestamp, data) {
    // Chop the large keyword list into small, bite-sized chunks for easier
    // digestion on the server.
    for (const chunk of pageArray(data.aaData, 50)) {
        yield bg.ajax(`${bg.serviceUrl}/api/keywordData/${entityId}/${adGroupId}?timestamp=${timestamp}`, {
            method: 'PUT',
            data: JSON.stringify({ aaData: chunk }),
            contentType: 'application/json',
        });
    }
}

const getDataHistory = bg.coMemo(function*(entityId, campaignId) { // TODO: date ranges, etc.
    checkEntityId(entityId);
    const snapshots = yield bg.ajax(`${bg.serviceUrl}/api/data/${entityId}/${campaignId}`, {
        method: 'GET',
        dataType: 'json'
    });
    return common.convertSnapshotsToDeltas(snapshots);
});

const getAggregateCampaignHistory = bg.coMemo(function*(entityId, campaignIds) {
    let aggregate = [];
    for (const page of pageArray(campaignIds, 6)) {
        const promises = [];
        for (const campaignId of page) {
            promises.push(co(function*() {
                let history = yield getDataHistory(entityId, campaignId);
                aggregate = aggregate.concat(...history);
            }));
        }
        yield Promise.all(promises);
    }

    return aggregate.sort((a, b) => a.timestamp - b.timestamp);
});

const getKeywordData = bg.coMemo(function*(entityId, adGroupId) {
    checkEntityId(entityId);
    const ajaxOptions = {
        url: `${bg.serviceUrl}/api/keywordData/${entityId}/${adGroupId}`,
        method: 'GET',
        dataType: 'json',
    };
    let data = yield bg.ajax(ajaxOptions);

    if (!data || data.length == 0) {
        // Possibly this is the first time we've ever seen this campaign. If so,
        // let's query Amazon and populate our own servers, and then come back.
        // This is very slow but should usually only happen once.
        yield* requestKeywordData(entityId, adGroupId);
        data = yield bg.ajax(ajaxOptions);
    }

    return data;
});

const getAggregateKeywordData = bg.coMemo(function*(entityId, adGroupIds) {
    let keywordSets = [];

    for (const page of pageArray(adGroupIds, 6)) {
        const kwSets = yield Promise.all(page.map(adGroupId => co(getKeywordData(entityId, adGroupId))));
        keywordSets = keywordSets.concat(kwSets);
    }

    return keywordSets;
});

const setCampaignMetadata = bg.coMemo(function*(entityId, campaignId, asin) {
    checkEntityId(entityId);
    return yield bg.ajax(`${bg.serviceUrl}/api/campaignMetadata/${entityId}/${campaignId}`, {
        method: 'PUT',
        data: JSON.stringify({ asin }),
        contentType: 'application/json',
    });
});

const setAdGroupMetadata = bg.coMemo(function*(entityId, adGroupId, campaignId) {
    checkEntityId(entityId);
    return yield bg.ajax(`${bg.serviceUrl}/api/adGroupMetadata/${entityId}/${adGroupId}`, {
        method: 'PUT',
        data: JSON.stringify({ campaignId }),
        contentType: 'application/json',
    });
});

function* getAdGroups(entityId) {
    checkEntityId(entityId);
    return yield bg.ajax(`${bg.serviceUrl}/api/adGroups/${entityId}`, {
        method: 'GET',
        dataType: 'json',
    });
}

function* updateKeyword(entityId, keywordIdList, operation, dataValues) {
    // TODO: the parameters to the Amazon API imply that you can pass more than
    // 1 keyword at a time, but testing this shows that doing so just generates
    // an error. So we do it the stupid way instead, with a loop.
    const timestamp = Date.now();

    const results = [];
    for (const chunk of pageArray(keywordIdList, 6)) {
        let requests = [];
        for (let id of chunk) {
            let postData = Object.assign({operation, entityId, keywordIds: id}, dataValues);
            requests.push(bg.ajax({
                url: 'https://ams.amazon.com/api/sponsored-products/updateKeywords/',
                method: 'POST',
                data: postData,
                dataType: 'json',
            }));
        }

        const responses = yield Promise.all(requests);
        const successes = chunk.filter((x, index) => responses[index].success);
        yield bg.ajax(`${bg.serviceUrl}/api/keywordData/${entityId}?timestamp=${timestamp}`, {
            method: 'PATCH',
            dataType: 'json',
            data: JSON.stringify({ operation, dataValues, keywordIds: successes }),
            contentType: 'application/json',
        });

        results.concat(...responses);
    }

    // TODO: in the case that we have a lot of these (bulk update), implement
    // progress feedback.
    return { success: results.every(x => x.success) };
}

let notificationExists = false;
function notifyNeedCredentials(entityId) {
    if (!notificationExists) {
        let notificationId = `machete-${entityId}-need-credentials`;
        chrome.notifications.create(notificationId, {
            type: "basic",
            iconUrl: "images/machete-128.png",
            title: "Sign in to AMS",
            message: "Machete needs you to sign in to AMS so it can keep your campaign history up-to-date.",
            contextMessage: "Click to sign in at https://ams.amazon.com/",
            isClickable: true,
            requireInteraction: true,
        });

        notificationExists = true;
        ga.mga('event', 'credential-popup', 'show');
        chrome.notifications.onClicked.addListener((clickId) => {
            if (clickId == notificationId) {
                ga.mga('event', 'credential-popup', 'click');
                chrome.tabs.create({ url: "https://ams.amazon.com/ads/dashboard" });
                chrome.notifications.clear(notificationId);
                notificationExists = false;
            }
        });
        chrome.notifications.onClosed.addListener(() => {
            notificationExists = false;
            ga.mga('event', 'credential-popup', 'dismiss');
        });
    }
}
<|endoftext|>