<commit_before>const axios = require('axios')
const _ = require('lodash/fp')
const hkdf = require('futoin-hkdf')

const pify = require('pify')
const fs = pify(require('fs'))

const db = require('../db')
const configManager = require('../config-manager')
const options = require('../options')
const logger = require('../logger')

const TIMEOUT = 10000
const MAX_CONTENT_LENGTH = 2000

// How long a machine can be down before it's considered offline
const STALE_INTERVAL = '2 minutes'

module.exports = {update, mapRecord}

function mapCoin (info, deviceId, cryptoCode) {
  const config = info.config
  const rates = info.rates[cryptoCode] || {cashIn: null, cashOut: null}
  const cryptoConfig = configManager.scoped(cryptoCode, deviceId, config)
  const unscoped = configManager.unscoped(config)
  const showRates = unscoped.coinAtmRadarShowRates

  const cashInFee = showRates ? cryptoConfig.cashInCommission / 100 : null
  const cashOutFee = showRates ? cryptoConfig.cashOutCommission / 100 : null
  const cashInRate = showRates ?  _.invoke('cashIn.toNumber', rates) : null
  const cashOutRate = showRates ? _.invoke('cashOut.toNumber', rates) : null

  return {
    cryptoCode,
    cashInFee,
    cashOutFee,
    cashInRate,
    cashOutRate
  }
}

function mapIdentification (info, deviceId) {
  const machineConfig = configManager.machineScoped(deviceId, info.config)

  return {
    isPhone: machineConfig.smsVerificationActive,
    isPalmVein: false,
    isPhoto: false,
    isIdDocScan: machineConfig.idCardDataVerificationActive,
    isFingerprint: false
  }
}

function mapMachine (info, machineRow) {
  const deviceId = machineRow.device_id
  const config = info.config
  const machineConfig = configManager.machineScoped(deviceId, config)

  const lastOnline = machineRow.last_online.toISOString()
  const status = machineRow.stale ? 'online' : 'offline'

  const cashLimit = machineConfig.hardLimitVerificationActive
    ? machineConfig.hardLimitVerificationThreshold
    : Infinity

  const cryptoCurrencies = machineConfig.cryptoCurrencies
  const identification = mapIdentification(info, deviceId)
  const coins = _.map(_.partial(mapCoin, [info, deviceId]), cryptoCurrencies)

  return {
    machineId: deviceId,
    address: {
      streetAddress: null,
      city: null,
      region: null,
      postalCode: null,
      country: null
    },
    location: {
      name: null,
      url: null,
      phone: null
    },
    status,
    lastOnline,
    cashIn: true,
    cashOut: machineConfig.cashOutEnabled,
    manufacturer: 'lamassu',
    cashInTxLimit: cashLimit,
    cashOutTxLimit: cashLimit,
    cashInDailyLimit: cashLimit,
    cashOutDailyLimit: cashLimit,
    fiatCurrency: machineConfig.fiatCurrency,
    identification,
    coins
  }
}

function getMachines (info) {
  const sql = `select device_id, last_online, now() - last_online < $1 as stale from devices
  where display=TRUE and
  paired=TRUE
  order by created`

  return db.any(sql, [STALE_INTERVAL])
    .then(_.map(_.partial(mapMachine, [info])))
}

function sendRadar (data) {
  const url = _.get(['coinAtmRadar', 'url'], options)

  if (_.isEmpty(url)) {
    return Promise.reject(new Error('Missing coinAtmRadar url!'))
  }

  const config = {
    url,
    method: 'post',
    data,
    timeout: TIMEOUT,
    maxContentLength: MAX_CONTENT_LENGTH
  }

  console.log('%j', data)

  return axios(config)
    .then(r => console.log(r.status))
}

function mapRecord (info) {
  const timestamp = new Date().toISOString()
  return Promise.all([getMachines(info), fs.readFile(options.seedPath, 'utf8')])
    .then(([machines, hex]) => ({
      operatorId: computeOperatorId(Buffer.from(hex.trim(), 'hex')),
      operator: {
        name: null,
        phone: null,
        email: null
      },
      timestamp,
      machines
    }))
}

function update (info) {
  const config = configManager.unscoped(info.config)

  if (!config.coinAtmRadarActive) return Promise.resolve()

  return mapRecord(info)
    .then(sendRadar)
    .catch(err => logger.error(err))
}

function computeOperatorId (masterSeed) {
  return hkdf(masterSeed, 32, {salt: 'lamassu-server-salt', info: 'operator-id'}).toString('hex')
}
<commit_msg>Change size of operatorId (#190)<commit_after>const axios = require('axios')
const _ = require('lodash/fp')
const hkdf = require('futoin-hkdf')

const pify = require('pify')
const fs = pify(require('fs'))

const db = require('../db')
const configManager = require('../config-manager')
const options = require('../options')
const logger = require('../logger')

const TIMEOUT = 10000
const MAX_CONTENT_LENGTH = 2000

// How long a machine can be down before it's considered offline
const STALE_INTERVAL = '2 minutes'

module.exports = { update, mapRecord }

function mapCoin (info, deviceId, cryptoCode) {
  const config = info.config
  const rates = info.rates[cryptoCode] || { cashIn: null, cashOut: null }
  const cryptoConfig = configManager.scoped(cryptoCode, deviceId, config)
  const unscoped = configManager.unscoped(config)
  const showRates = unscoped.coinAtmRadarShowRates

  const cashInFee = showRates ? cryptoConfig.cashInCommission / 100 : null
  const cashOutFee = showRates ? cryptoConfig.cashOutCommission / 100 : null
  const cashInRate = showRates ? _.invoke('cashIn.toNumber', rates) : null
  const cashOutRate = showRates ? _.invoke('cashOut.toNumber', rates) : null

  return {
    cryptoCode,
    cashInFee,
    cashOutFee,
    cashInRate,
    cashOutRate
  }
}

function mapIdentification (info, deviceId) {
  const machineConfig = configManager.machineScoped(deviceId, info.config)

  return {
    isPhone: machineConfig.smsVerificationActive,
    isPalmVein: false,
    isPhoto: false,
    isIdDocScan: machineConfig.idCardDataVerificationActive,
    isFingerprint: false
  }
}

function mapMachine (info, machineRow) {
  const deviceId = machineRow.device_id
  const config = info.config
  const machineConfig = configManager.machineScoped(deviceId, config)

  const lastOnline = machineRow.last_online.toISOString()
  const status = machineRow.stale ? 'online' : 'offline'

  const cashLimit = machineConfig.hardLimitVerificationActive
    ? machineConfig.hardLimitVerificationThreshold
    : Infinity

  const cryptoCurrencies = machineConfig.cryptoCurrencies
  const identification = mapIdentification(info, deviceId)
  const coins = _.map(_.partial(mapCoin, [info, deviceId]), cryptoCurrencies)

  return {
    machineId: deviceId,
    address: {
      streetAddress: null,
      city: null,
      region: null,
      postalCode: null,
      country: null
    },
    location: {
      name: null,
      url: null,
      phone: null
    },
    status,
    lastOnline,
    cashIn: true,
    cashOut: machineConfig.cashOutEnabled,
    manufacturer: 'lamassu',
    cashInTxLimit: cashLimit,
    cashOutTxLimit: cashLimit,
    cashInDailyLimit: cashLimit,
    cashOutDailyLimit: cashLimit,
    fiatCurrency: machineConfig.fiatCurrency,
    identification,
    coins
  }
}

function getMachines (info) {
  const sql = `select device_id, last_online, now() - last_online < $1 as stale from devices
  where display=TRUE and
  paired=TRUE
  order by created`

  return db.any(sql, [STALE_INTERVAL])
    .then(_.map(_.partial(mapMachine, [info])))
}

function sendRadar (data) {
  const url = _.get(['coinAtmRadar', 'url'], options)

  if (_.isEmpty(url)) {
    return Promise.reject(new Error('Missing coinAtmRadar url!'))
  }

  const config = {
    url,
    method: 'post',
    data,
    timeout: TIMEOUT,
    maxContentLength: MAX_CONTENT_LENGTH
  }

  console.log('%j', data)

  return axios(config)
    .then(r => console.log(r.status))
}

function mapRecord (info) {
  const timestamp = new Date().toISOString()
  return Promise.all([getMachines(info), fs.readFile(options.seedPath, 'utf8')])
    .then(([machines, hex]) => ({
      operatorId: computeOperatorId(Buffer.from(hex.trim(), 'hex')),
      operator: {
        name: null,
        phone: null,
        email: null
      },
      timestamp,
      machines
    }))
}

function update (info) {
  const config = configManager.unscoped(info.config)

  if (!config.coinAtmRadarActive) return Promise.resolve()

  return mapRecord(info)
    .then(sendRadar)
    .catch(err => logger.error(err))
}

function computeOperatorId (masterSeed) {
  return hkdf(masterSeed, 16, { salt: 'lamassu-server-salt', info: 'operator-id' }).toString('hex')
}
<|endoftext|>


<commit_before>// Most of this is taken from the service manual and at some point needs to be
// separated out into a component.  We are currently holding off on this until
// it is tested more within the current navigation changes.

window.GOVUK.Modules = window.GOVUK.Modules || {};

(function (Modules) {
  "use strict";

  Modules.AccordionWithDescriptions = function () {

    var bulkActions = {
      openAll: {
        buttonText: "Expand all",
        eventLabel: "Open All"
      },
      closeAll: {
        buttonText: "Close all",
        eventLabel: "Close All"
      }
    };

    this.start = function ($element) {

      // Indicate that js has worked
      $element.addClass('js-accordion-with-descriptions');

      // Prevent FOUC, remove class hiding content
      $element.removeClass('js-hidden');

      var $subsections = $element.find('.js-subsection');
      var $subsectionHeaders = $element.find('.subsection-header');
      var totalSubsections = $element.find('.subsection-content').length;

      var $openOrCloseAllButton;

      wrapHeadersInLinks();
      addOpenCloseAllButton();
      addIconsToSubsections();
      addAriaControlsAttrForOpenCloseAllButton();

      closeAllSections();
      openLinkedSection();

      bindToggleForSubsections();
      bindToggleOpenCloseAllButton();

      function addOpenCloseAllButton() {
        $element.prepend('
<button aria-expanded="false">' + bulkActions.openAll.buttonText + '</button>
');
      }

      function addIconsToSubsections() {
        $subsectionHeaders.append('');
      }

      function addAriaControlsAttrForOpenCloseAllButton() {
        var ariaControlsValue = "";
        for (var i = 0; i < totalSubsections; i++) {
          ariaControlsValue += "subsection_content_" + i + " ";
        }

        $openOrCloseAllButton = $element.find('.js-subsection-controls button');
        $openOrCloseAllButton.attr('aria-controls', ariaControlsValue);
      }

      function closeAllSections() {
        setAllSectionsOpenState(false);
      }

      function setAllSectionsOpenState(isOpen) {
        $.each($subsections, function () {
          var subsectionView = new SubsectionView($(this));
          subsectionView.preventHashUpdate();
          subsectionView.setIsOpen(isOpen);
        });
      }

      function openLinkedSection() {
        var anchor = getActiveAnchor(),
          $subsection;

        if (!anchor.length) {
          return;
        }

        anchor = '#' + escapeSelector(anchor.substr(1));
        $subsection = $element.find(anchor);

        if ($subsection.length) {
          var subsectionView = new SubsectionView($subsection);
          subsectionView.open();
        }
      }

      function getActiveAnchor() {
        return GOVUK.getCurrentLocation().hash;
      }

      function wrapHeadersInLinks() {
        $.each($subsections, function () {
          var $subsection = $(this);
          var id = $subsection.attr('id');
          var $title = $subsection.find('.js-subsection-title');
          var contentId = $subsection.find('.js-subsection-content').first().attr('id');

          $title.wrap(
            ''
          )
        });
      }

      function bindToggleForSubsections() {
        $element.find('.js-subsection-header').click(function (event) {
          event.preventDefault();

          var subsectionView = new SubsectionView($(this).closest('.js-subsection'));
          subsectionView.toggle();

          var toggleClick = new SubsectionToggleClick(subsectionView, event);
          toggleClick.track();

          setOpenCloseAllText();
        });
      }

      function bindToggleOpenCloseAllButton() {
        $openOrCloseAllButton = $element.find('.js-subsection-controls button');
        $openOrCloseAllButton.on('click', function () {
          var shouldOpenAll;

          if ($openOrCloseAllButton.text() == bulkActions.openAll.buttonText) {
            $openOrCloseAllButton.text(bulkActions.closeAll.buttonText);
            shouldOpenAll = true;

            track('pageElementInteraction', 'accordionAllOpened', {
              label: bulkActions.openAll.eventLabel
            });
          } else {
            $openOrCloseAllButton.text(bulkActions.openAll.buttonText);
            shouldOpenAll = false;

            track('pageElementInteraction', 'accordionAllClosed', {
              label: bulkActions.closeAll.eventLabel
            });
          }

          setAllSectionsOpenState(shouldOpenAll);
          $openOrCloseAllButton.attr('aria-expanded', shouldOpenAll);
          setOpenCloseAllText();
          setHash(null);

          return false;
        });
      }

      function setOpenCloseAllText() {
        var openSubsections = $element.find('.subsection-is-open').length;
        // Find out if the number of is-opens == total number of sections
        if (openSubsections === totalSubsections) {
          $openOrCloseAllButton.text(bulkActions.closeAll.buttonText);
        } else {
          $openOrCloseAllButton.text(bulkActions.openAll.buttonText);
        }
      }

      // Ideally we'd use jQuery.escapeSelector, but this is only available from v3
      // See https://github.com/jquery/jquery/blob/2d4f53416e5f74fa98e0c1d66b6f3c285a12f0ce/src/selector-native.js#L46
      function escapeSelector(s) {
        var cssMatcher = /([\x00-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;
        return s.replace(cssMatcher, "\\$&");
      }
    };

    function SubsectionView($subsectionElement) {
      var $subsectionContent = $subsectionElement.find('.js-subsection-content');
      var $titleLink = $subsectionElement.find('.js-subsection-title-link');
      var shouldUpdateHash = true;

      this.title = $subsectionElement.find('.js-subsection-title').text();

      this.open = open;
      this.close = close;
      this.toggle = toggle;
      this.setIsOpen = setIsOpen;
      this.isOpen = isOpen;
      this.isClosed = isClosed;
      this.preventHashUpdate = preventHashUpdate;

      function open() {
        setIsOpen(true);
      }

      function close() {
        setIsOpen(false);
      }

      function toggle() {
        setIsOpen(isClosed());
      }

      function setIsOpen(isOpen) {
        $subsectionContent.toggleClass('js-hidden', !isOpen);
        $subsectionElement.toggleClass('subsection-is-open', isOpen);
        $titleLink.attr("aria-expanded", isOpen);

        if (shouldUpdateHash) {
          updateHash($subsectionElement);
        }
      }

      function isOpen() {
        return !isClosed();
      }

      function isClosed() {
        return $subsectionContent.hasClass('js-hidden');
      }

      function preventHashUpdate() {
        shouldUpdateHash = false;
      }
    }

    function updateHash($subsectionElement) {
      if (!GOVUK.support.history()) {
        return;
      }

      var subsectionView = new SubsectionView($subsectionElement);
      var hash = subsectionView.isOpen() && '#' + $subsectionElement.attr('id');
      setHash(hash)
    }

    // Sets the hash for the page. If a falsy value is provided, the hash is cleared.
    function setHash(hash) {
      var newLocation = hash || GOVUK.getCurrentLocation().pathname;
      history.replaceState({}, '', newLocation);
    }

    // A constructor for an object that represents a click event on a subsection which
    // handles the complexity of sending different tracking labels to Google Analytics
    // depending on which part of the subsection the user clicked.
    function SubsectionToggleClick(subsectionView, event) {
      var $target = $(event.target);

      this.track = trackClick;

      function trackClick() {
        track('pageElementInteraction', trackingAction(), {label: trackingLabel()});
      }

      function trackingAction() {
        return (subsectionView.isClosed() ? 'accordionClosed' : 'accordionOpened');
      }

      function trackingLabel() {
        if (clickedOnIcon()) {
          return subsectionView.title + ' - ' + iconType() + ' Click';
        } else if (clickedOnHeading()) {
          return subsectionView.title + ' - Heading Click';
        } else {
          return subsectionView.title + ' - Click Elsewhere';
        }
      }

      function clickedOnIcon() {
        return $target.hasClass('subsection-icon');
      }

      function clickedOnHeading() {
        return $target.hasClass('js-subsection-title-link');
      }

      function iconType() {
        return (subsectionView.isClosed() ? 'Minus' : 'Plus');
      }
    }

    // A helper that sends an custom event request to Google Analytics if
    // the GOVUK module is setup
    function track(category, action, options) {
      if (GOVUK.analytics && GOVUK.analytics.trackEvent) {
        GOVUK.analytics.trackEvent(category, action, options);
      }
    }
  };
})(window.GOVUK.Modules);
<commit_msg>Fix accordion for pages with no JS history object<commit_after>// Most of this is taken from the service manual and at some point needs to be
// separated out into a component.  We are currently holding off on this until
// it is tested more within the current navigation changes.

window.GOVUK.Modules = window.GOVUK.Modules || {};

(function (Modules) {
  "use strict";

  Modules.AccordionWithDescriptions = function () {

    var bulkActions = {
      openAll: {
        buttonText: "Expand all",
        eventLabel: "Open All"
      },
      closeAll: {
        buttonText: "Close all",
        eventLabel: "Close All"
      }
    };

    this.start = function ($element) {

      // Indicate that js has worked
      $element.addClass('js-accordion-with-descriptions');

      // Prevent FOUC, remove class hiding content
      $element.removeClass('js-hidden');

      var $subsections = $element.find('.js-subsection');
      var $subsectionHeaders = $element.find('.subsection-header');
      var totalSubsections = $element.find('.subsection-content').length;

      var $openOrCloseAllButton;

      wrapHeadersInLinks();
      addOpenCloseAllButton();
      addIconsToSubsections();
      addAriaControlsAttrForOpenCloseAllButton();

      closeAllSections();
      openLinkedSection();

      bindToggleForSubsections();
      bindToggleOpenCloseAllButton();

      function addOpenCloseAllButton() {
        $element.prepend('
<button aria-expanded="false">' + bulkActions.openAll.buttonText + '</button>
');
      }

      function addIconsToSubsections() {
        $subsectionHeaders.append('');
      }

      function addAriaControlsAttrForOpenCloseAllButton() {
        var ariaControlsValue = "";
        for (var i = 0; i < totalSubsections; i++) {
          ariaControlsValue += "subsection_content_" + i + " ";
        }

        $openOrCloseAllButton = $element.find('.js-subsection-controls button');
        $openOrCloseAllButton.attr('aria-controls', ariaControlsValue);
      }

      function closeAllSections() {
        setAllSectionsOpenState(false);
      }

      function setAllSectionsOpenState(isOpen) {
        $.each($subsections, function () {
          var subsectionView = new SubsectionView($(this));
          subsectionView.preventHashUpdate();
          subsectionView.setIsOpen(isOpen);
        });
      }

      function openLinkedSection() {
        var anchor = getActiveAnchor(),
          $subsection;

        if (!anchor.length) {
          return;
        }

        anchor = '#' + escapeSelector(anchor.substr(1));
        $subsection = $element.find(anchor);

        if ($subsection.length) {
          var subsectionView = new SubsectionView($subsection);
          subsectionView.open();
        }
      }

      function getActiveAnchor() {
        return GOVUK.getCurrentLocation().hash;
      }

      function wrapHeadersInLinks() {
        $.each($subsections, function () {
          var $subsection = $(this);
          var id = $subsection.attr('id');
          var $title = $subsection.find('.js-subsection-title');
          var contentId = $subsection.find('.js-subsection-content').first().attr('id');

          $title.wrap(
            ''
          )
        });
      }

      function bindToggleForSubsections() {
        $element.find('.js-subsection-header').click(function (event) {
          event.preventDefault();

          var subsectionView = new SubsectionView($(this).closest('.js-subsection'));
          subsectionView.toggle();

          var toggleClick = new SubsectionToggleClick(subsectionView, event);
          toggleClick.track();

          setOpenCloseAllText();
        });
      }

      function bindToggleOpenCloseAllButton() {
        $openOrCloseAllButton = $element.find('.js-subsection-controls button');
        $openOrCloseAllButton.on('click', function () {
          var shouldOpenAll;

          if ($openOrCloseAllButton.text() == bulkActions.openAll.buttonText) {
            $openOrCloseAllButton.text(bulkActions.closeAll.buttonText);
            shouldOpenAll = true;

            track('pageElementInteraction', 'accordionAllOpened', {
              label: bulkActions.openAll.eventLabel
            });
          } else {
            $openOrCloseAllButton.text(bulkActions.openAll.buttonText);
            shouldOpenAll = false;

            track('pageElementInteraction', 'accordionAllClosed', {
              label: bulkActions.closeAll.eventLabel
            });
          }

          setAllSectionsOpenState(shouldOpenAll);
          $openOrCloseAllButton.attr('aria-expanded', shouldOpenAll);
          setOpenCloseAllText();
          setHash(null);

          return false;
        });
      }

      function setOpenCloseAllText() {
        var openSubsections = $element.find('.subsection-is-open').length;
        // Find out if the number of is-opens == total number of sections
        if (openSubsections === totalSubsections) {
          $openOrCloseAllButton.text(bulkActions.closeAll.buttonText);
        } else {
          $openOrCloseAllButton.text(bulkActions.openAll.buttonText);
        }
      }

      // Ideally we'd use jQuery.escapeSelector, but this is only available from v3
      // See https://github.com/jquery/jquery/blob/2d4f53416e5f74fa98e0c1d66b6f3c285a12f0ce/src/selector-native.js#L46
      function escapeSelector(s) {
        var cssMatcher = /([\x00-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;
        return s.replace(cssMatcher, "\\$&");
      }
    };

    function SubsectionView($subsectionElement) {
      var $subsectionContent = $subsectionElement.find('.js-subsection-content');
      var $titleLink = $subsectionElement.find('.js-subsection-title-link');
      var shouldUpdateHash = true;

      this.title = $subsectionElement.find('.js-subsection-title').text();

      this.open = open;
      this.close = close;
      this.toggle = toggle;
      this.setIsOpen = setIsOpen;
      this.isOpen = isOpen;
      this.isClosed = isClosed;
      this.preventHashUpdate = preventHashUpdate;

      function open() {
        setIsOpen(true);
      }

      function close() {
        setIsOpen(false);
      }

      function toggle() {
        setIsOpen(isClosed());
      }

      function setIsOpen(isOpen) {
        $subsectionContent.toggleClass('js-hidden', !isOpen);
        $subsectionElement.toggleClass('subsection-is-open', isOpen);
        $titleLink.attr("aria-expanded", isOpen);

        if (shouldUpdateHash) {
          updateHash($subsectionElement);
        }
      }

      function isOpen() {
        return !isClosed();
      }

      function isClosed() {
        return $subsectionContent.hasClass('js-hidden');
      }

      function preventHashUpdate() {
        shouldUpdateHash = false;
      }
    }

    function updateHash($subsectionElement) {
      var subsectionView = new SubsectionView($subsectionElement);
      var hash = subsectionView.isOpen() && '#' + $subsectionElement.attr('id');
      setHash(hash)
    }

    // Sets the hash for the page. If a falsy value is provided, the hash is cleared.
    function setHash(hash) {
      if (!GOVUK.support.history()) {
        return;
      }

      var newLocation = hash || GOVUK.getCurrentLocation().pathname;
      history.replaceState({}, '', newLocation);
    }

    // A constructor for an object that represents a click event on a subsection which
    // handles the complexity of sending different tracking labels to Google Analytics
    // depending on which part of the subsection the user clicked.
    function SubsectionToggleClick(subsectionView, event) {
      var $target = $(event.target);

      this.track = trackClick;

      function trackClick() {
        track('pageElementInteraction', trackingAction(), {label: trackingLabel()});
      }

      function trackingAction() {
        return (subsectionView.isClosed() ? 'accordionClosed' : 'accordionOpened');
      }

      function trackingLabel() {
        if (clickedOnIcon()) {
          return subsectionView.title + ' - ' + iconType() + ' Click';
        } else if (clickedOnHeading()) {
          return subsectionView.title + ' - Heading Click';
        } else {
          return subsectionView.title + ' - Click Elsewhere';
        }
      }

      function clickedOnIcon() {
        return $target.hasClass('subsection-icon');
      }

      function clickedOnHeading() {
        return $target.hasClass('js-subsection-title-link');
      }

      function iconType() {
        return (subsectionView.isClosed() ? 'Minus' : 'Plus');
      }
    }

    // A helper that sends an custom event request to Google Analytics if
    // the GOVUK module is setup
    function track(category, action, options) {
      if (GOVUK.analytics && GOVUK.analytics.trackEvent) {
        GOVUK.analytics.trackEvent(category, action, options);
      }
    }
  };
})(window.GOVUK.Modules);
<|endoftext|>


<commit_before>'use strict';

const angular = require('angular');
import _ from 'lodash';

import { FirewallLabels, INSTANCE_TYPE_SERVICE, ModalWizard, TaskMonitor } from '@spinnaker/core';

import { parseHealthCheckUrl } from 'google/healthCheck/healthCheckUtils';

module.exports = angular
  .module('spinnaker.serverGroup.configure.gce.cloneServerGroup', [
    require('@uirouter/angularjs').default,
    require('google/instance/custom/customInstanceBuilder.gce.service.js').name,
    INSTANCE_TYPE_SERVICE,
    require('./hiddenMetadataKeys.value.js').name,
    require('./securityGroups/tagManager.service.js').name,
  ])
  .controller('gceCloneServerGroupCtrl', function(
    $scope,
    $uibModalInstance,
    $q,
    $state,
    $log,
    serverGroupWriter,
    gceServerGroupConfigurationService,
    serverGroupCommand,
    application,
    title,
    gceCustomInstanceBuilderService,
    instanceTypeService,
    wizardSubFormValidation,
    gceServerGroupHiddenMetadataKeys,
    gceTagManager,
  ) {
    $scope.pages = {
      templateSelection: require('./templateSelection/templateSelection.html'),
      basicSettings: require('./location/basicSettings.html'),
      loadBalancers: require('./loadBalancers/loadBalancers.html'),
      securityGroups: require('./securityGroups/securityGroups.html'),
      instanceType: require('./instanceType/instanceType.html'),
      capacity: require('./capacity/capacity.html'),
      zones: require('./capacity/zones.html'),
      autoHealingPolicy: require('./autoHealingPolicy/autoHealingPolicy.html'),
      advancedSettings: require('./advancedSettings/advancedSettings.html'),
    };

    $scope.firewallsLabel = FirewallLabels.get('Firewalls');

    $scope.title = title;

    $scope.applicationName = application.name;
    $scope.application = application;

    $scope.command = serverGroupCommand;

    $scope.state = {
      loaded: false,
      requiresTemplateSelection: !!serverGroupCommand.viewState.requiresTemplateSelection,
    };

    this.templateSelectionText = {
      copied: [
        'account, region, subnet, cluster name (stack, details)',
        'load balancers',
        FirewallLabels.get('firewalls'),
        'instance type',
        'all fields on the Advanced Settings page',
      ],
      notCopied: [],
    };

    if (!$scope.command.viewState.disableStrategySelection) {
      this.templateSelectionText.notCopied.push(
        'the deployment strategy (if any) used to deploy the most recent server group',
      );
    }

    function onApplicationRefresh() {
      // If the user has already closed the modal, do not navigate to the new details view
      if ($scope.$$destroyed) {
        return;
      }
      const cloneStage = $scope.taskMonitor.task.execution.stages.find(stage => stage.type === 'cloneServerGroup');
      if (cloneStage && cloneStage.context['deploy.server.groups']) {
        const newServerGroupName = cloneStage.context['deploy.server.groups'][$scope.command.region];
        if (newServerGroupName) {
          const newStateParams = {
            serverGroup: newServerGroupName,
            accountId: $scope.command.credentials,
            region: $scope.command.region,
            provider: 'gce',
          };
          let transitionTo = '^.^.^.clusters.serverGroup';
          if ($state.includes('**.clusters.serverGroup')) {
            // clone via details, all view
            transitionTo = '^.serverGroup';
          }
          if ($state.includes('**.clusters.cluster.serverGroup')) {
            // clone or create with details open
            transitionTo = '^.^.serverGroup';
          }
          if ($state.includes('**.clusters')) {
            // create new, no details open
            transitionTo = '.serverGroup';
          }
          $state.go(transitionTo, newStateParams);
        }
      }
    }

    function onTaskComplete() {
      application.serverGroups.refresh();
      application.serverGroups.onNextRefresh($scope, onApplicationRefresh);
    }

    $scope.taskMonitor = new TaskMonitor({
      application: application,
      title: 'Creating your server group',
      modalInstance: $uibModalInstance,
      onTaskComplete: onTaskComplete,
    });

    function configureCommand() {
      gceServerGroupConfigurationService
        .configureCommand(application, serverGroupCommand)
        .then(function() {
          const mode = serverGroupCommand.viewState.mode;
          if (mode === 'clone' || mode === 'create') {
            if (!serverGroupCommand.backingData.packageImages || !serverGroupCommand.backingData.packageImages.length) {
              serverGroupCommand.viewState.useAllImageSelection = true;
            }
          }
          $scope.state.loaded = true;
          initializeSelectOptions();
          initializeWatches();
          wizardSubFormValidation
            .config({ scope: $scope, form: 'form' })
            .register({ page: 'location', subForm: 'basicSettings' })
            .register({ page: 'capacity', subForm: 'capacitySubForm' })
            .register({ page: 'zones', subForm: 'zonesSubForm' })
            .register({ page: 'load-balancers', subForm: 'loadBalancerSubForm' })
            .register({ page: 'autohealing-policy', subForm: 'autoHealingPolicySubForm' });
        })
        .catch(e => {
          $log.error('Error generating server group command: ', e);
        });
    }

    function initializeWatches() {
      $scope.$watch('command.credentials', createResultProcessor($scope.command.credentialsChanged));
      $scope.$watch('command.regional', createResultProcessor($scope.command.regionalChanged));
      $scope.$watch('command.region', createResultProcessor($scope.command.regionChanged));
      $scope.$watch('command.network', createResultProcessor($scope.command.networkChanged));
      $scope.$watch('command.zone', createResultProcessor($scope.command.zoneChanged));
      $scope.$watch('command.viewState.instanceTypeDetails', updateStorageSettingsFromInstanceType());
      $scope.$watch(
        'command.viewState.customInstance',
        () => {
          $scope.command.customInstanceChanged($scope.command);
          setInstanceTypeFromCustomChoices();
        },
        true,
      );
    }

    function initializeSelectOptions() {
      processCommandUpdateResult($scope.command.credentialsChanged($scope.command));
      processCommandUpdateResult($scope.command.regionalChanged($scope.command));
      processCommandUpdateResult($scope.command.regionChanged($scope.command));
      processCommandUpdateResult($scope.command.networkChanged($scope.command));
      processCommandUpdateResult($scope.command.zoneChanged($scope.command));
      processCommandUpdateResult($scope.command.customInstanceChanged($scope.command));
      gceServerGroupConfigurationService.configureSubnets($scope.command);
    }

    function createResultProcessor(method) {
      return function() {
        processCommandUpdateResult(method($scope.command));
      };
    }

    function processCommandUpdateResult(result) {
      if (result.dirty.loadBalancers) {
        ModalWizard.markDirty('load-balancers');
      }
      if (result.dirty.securityGroups) {
        ModalWizard.markDirty('security-groups');
      }
      if (result.dirty.availabilityZones) {
        ModalWizard.markDirty('capacity');
      }
      if (result.dirty.instanceType) {
        ModalWizard.markDirty('instance-type');
      }
    }

    function setInstanceTypeFromCustomChoices() {
      const c = $scope.command,
        location = c.regional ? c.region : c.zone,
        { locationToInstanceTypesMap } = c.backingData.credentialsKeyedByAccount[c.credentials];

      const customInstanceChoices = [
        _.get(c, 'viewState.customInstance.vCpuCount'),
        _.get(c, 'viewState.customInstance.memory'),
      ];

      if (
        _.every([
          ...customInstanceChoices,
          gceCustomInstanceBuilderService.customInstanceChoicesAreValid(
            ...customInstanceChoices,
            location,
            locationToInstanceTypesMap,
          ),
        ])
      ) {
        c.instanceType = gceCustomInstanceBuilderService.generateInstanceTypeString(...customInstanceChoices);

        instanceTypeService.getInstanceTypeDetails(c.selectedProvider, 'buildCustom').then(instanceTypeDetails => {
          c.viewState.instanceTypeDetails = instanceTypeDetails;
        });
      }
    }

    function updateStorageSettingsFromInstanceType() {
      return function(instanceTypeDetails) {
        if ($scope.command.viewState.initialized) {
          if (instanceTypeDetails && instanceTypeDetails.storage && instanceTypeDetails.storage.defaultSettings) {
            $scope.command.disks = instanceTypeDetails.storage.defaultSettings.disks;
            delete $scope.command.viewState.overriddenStorageDescription;
          }
        } else {
          $scope.command.viewState.initialized = true;
        }
      };
    }

    this.isValid = function() {
      const selectedZones = $scope.command.selectZones && _.get($scope, 'command.distributionPolicy.zones.length') >= 1;
      return (
        $scope.command &&
        ($scope.command.viewState.disableImageSelection || $scope.command.image) &&
        $scope.command.application &&
        $scope.command.credentials &&
        $scope.command.instanceType &&
        $scope.command.region &&
        ($scope.command.regional || $scope.command.zone) &&
        $scope.command.capacity.desired !== null &&
        (!$scope.command.selectZones || selectedZones) &&
        $scope.form.$valid &&
        ModalWizard.isComplete()
      );
    };

    this.showSubmitButton = function() {
      return ModalWizard.allPagesVisited();
    };

    function buildLoadBalancerMetadata(loadBalancerNames, loadBalancerIndex, backendServices) {
      let metadata = {};

      if (_.get(loadBalancerNames, 'length') > 0) {
        metadata = loadBalancerNames.reduce(
          (metadata, name) => {
            const loadBalancerDetails = loadBalancerIndex[name];

            if (loadBalancerDetails.loadBalancerType === 'HTTP') {
              metadata['global-load-balancer-names'] = metadata['global-load-balancer-names'].concat(
                loadBalancerDetails.listeners.map(listener => listener.name),
              );
            } else if (loadBalancerDetails.loadBalancerType === 'SSL') {
              metadata['global-load-balancer-names'].push(name);
            } else if (loadBalancerDetails.loadBalancerType === 'TCP') {
              metadata['global-load-balancer-names'].push(name);
            } else {
              metadata['load-balancer-names'].push(name);
            }
            return metadata;
          },
          { 'load-balancer-names': [], 'global-load-balancer-names': [] },
        );
      }

      if (_.isObject(backendServices) && Object.keys(backendServices).length > 0) {
        metadata['backend-service-names'] = _.reduce(
          backendServices,
          (accumulatedBackends, backends) => accumulatedBackends.concat(backends),
          [],
        );
      }

      for (const key in metadata) {
        if (metadata[key].length === 0) {
          delete metadata[key];
        } else {
          metadata[key] = _.uniq(metadata[key]).toString();
        }
      }

      return metadata;
    }

    function collectLoadBalancerNamesForCommand(loadBalancerIndex, loadBalancerMetadata) {
      let loadBalancerNames = [];
      if (loadBalancerMetadata['load-balancer-names']) {
        loadBalancerNames = loadBalancerNames.concat(loadBalancerMetadata['load-balancer-names'].split(','));
      }

      const selectedSslLoadBalancerNames = _.chain(loadBalancerIndex)
        .filter({ loadBalancerType: 'SSL' })
        .map('name')
        .intersection(
          loadBalancerMetadata['global-load-balancer-names']
            ? loadBalancerMetadata['global-load-balancer-names'].split(',')
            : [],
        )
        .value();

      const selectedTcpLoadBalancerNames = _.chain(loadBalancerIndex)
        .filter({ loadBalancerType: 'TCP' })
        .map('name')
        .intersection(
          loadBalancerMetadata['global-load-balancer-names']
            ? loadBalancerMetadata['global-load-balancer-names'].split(',')
            : [],
        )
        .value();

      return loadBalancerNames.concat(selectedSslLoadBalancerNames).concat(selectedTcpLoadBalancerNames);
    }

    this.submit = function() {
      // We use this list of load balancer names when 'Enabling' a server group.
      const loadBalancerMetadata = buildLoadBalancerMetadata(
        $scope.command.loadBalancers,
        $scope.command.backingData.filtered.loadBalancerIndex,
        $scope.command.backendServices,
      );

      const origLoadBalancers = $scope.command.loadBalancers;
      $scope.command.loadBalancers = collectLoadBalancerNamesForCommand(
        $scope.command.backingData.filtered.loadBalancerIndex,
        loadBalancerMetadata,
      );

      if ($scope.command.minCpuPlatform === '(Automatic)') {
        $scope.command.minCpuPlatform = '';
      }

      angular.extend($scope.command.instanceMetadata, loadBalancerMetadata);

      const origTags = $scope.command.tags;
      const transformedTags = [];
      // The tags are stored using a 'value' attribute to enable the Add/Remove behavior in the wizard.
      $scope.command.tags.forEach(function(tag) {
        transformedTags.push(tag.value);
      });
      $scope.command.tags = transformedTags;

      $scope.command.targetSize = $scope.command.capacity.desired;

      if ($scope.command.autoscalingPolicy) {
        $scope.command.capacity.min = $scope.command.autoscalingPolicy.minNumReplicas;
        $scope.command.capacity.max = $scope.command.autoscalingPolicy.maxNumReplicas;
      } else {
        $scope.command.capacity.min = $scope.command.capacity.desired;
        $scope.command.capacity.max = $scope.command.capacity.desired;
      }

      delete $scope.command.securityGroups;

      if ($scope.command.viewState.mode === 'editPipeline' || $scope.command.viewState.mode === 'createPipeline') {
        return $uibModalInstance.close($scope.command);
      }

      const healthCheckUrl = $scope.command.autoHealingPolicy.healthCheck;
      if (healthCheckUrl) {
        const { healthCheckName, healthCheckKind } = parseHealthCheckUrl(healthCheckUrl);
        $scope.command.autoHealingPolicy.healthCheck = healthCheckName;
        $scope.command.autoHealingPolicy.healthCheckKind = healthCheckKind;
      }

      $scope.taskMonitor.submit(function() {
        const promise = serverGroupWriter.cloneServerGroup(angular.copy($scope.command), application);

        // Copy back the original objects so the wizard can still be used if the command needs to be resubmitted.
        $scope.command.instanceMetadata = _.omit($scope.command.instanceMetadata, gceServerGroupHiddenMetadataKeys);

        $scope.command.tags = origTags;
        $scope.command.loadBalancers = origLoadBalancers;
        $scope.command.securityGroups = gceTagManager.inferSecurityGroupIdsFromTags($scope.command.tags);
        $scope.command.autoHealingPolicy.healthCheck = healthCheckUrl;

        return promise;
      });
    };

    this.onHealthCheckRefresh = function() {
      gceServerGroupConfigurationService.refreshHealthChecks($scope.command);
    };

    this.onEnableAutoHealingChange = function() {
      // Prevent empty auto-healing policies from being overwritten by those of their ancestors
      $scope.command.overwriteAncestorAutoHealingPolicy =
        $scope.command.viewState.mode === 'clone' &&
        $scope.command.autoHealingPolicy != null &&
        $scope.command.enableAutoHealing === false;
    };

    this.setAutoHealingPolicy = function(autoHealingPolicy) {
      $scope.command.autoHealingPolicy = autoHealingPolicy;
    };

    this.cancel = function() {
      $uibModalInstance.dismiss();
    };

    this.specialInstanceProfiles = new Set(['custom', 'buildCustom']);

    // This function is called from within React, and without $apply, Angular does not know when it has been called.
    $scope.command.setCustomInstanceViewState = customInstanceChoices => {
      $scope.$apply(() => ($scope.command.viewState.customInstance = customInstanceChoices));
    };

    if (!$scope.state.requiresTemplateSelection) {
      configureCommand();
    } else {
      $scope.state.loaded = true;
    }

    this.templateSelected = () => {
      $scope.state.requiresTemplateSelection = false;
      configureCommand();
    };

    $scope.$on('$destroy', gceTagManager.reset);
  });
<commit_msg>fix(google): safe healthcheck lookups when cloning server group<commit_after>'use strict';

const angular = require('angular');
import _ from 'lodash';

import { FirewallLabels, INSTANCE_TYPE_SERVICE, ModalWizard, TaskMonitor } from '@spinnaker/core';

import { parseHealthCheckUrl } from 'google/healthCheck/healthCheckUtils';

module.exports = angular
  .module('spinnaker.serverGroup.configure.gce.cloneServerGroup', [
    require('@uirouter/angularjs').default,
    require('google/instance/custom/customInstanceBuilder.gce.service.js').name,
    INSTANCE_TYPE_SERVICE,
    require('./hiddenMetadataKeys.value.js').name,
    require('./securityGroups/tagManager.service.js').name,
  ])
  .controller('gceCloneServerGroupCtrl', function(
    $scope,
    $uibModalInstance,
    $q,
    $state,
    $log,
    serverGroupWriter,
    gceServerGroupConfigurationService,
    serverGroupCommand,
    application,
    title,
    gceCustomInstanceBuilderService,
    instanceTypeService,
    wizardSubFormValidation,
    gceServerGroupHiddenMetadataKeys,
    gceTagManager,
  ) {
    $scope.pages = {
      templateSelection: require('./templateSelection/templateSelection.html'),
      basicSettings: require('./location/basicSettings.html'),
      loadBalancers: require('./loadBalancers/loadBalancers.html'),
      securityGroups: require('./securityGroups/securityGroups.html'),
      instanceType: require('./instanceType/instanceType.html'),
      capacity: require('./capacity/capacity.html'),
      zones: require('./capacity/zones.html'),
      autoHealingPolicy: require('./autoHealingPolicy/autoHealingPolicy.html'),
      advancedSettings: require('./advancedSettings/advancedSettings.html'),
    };

    $scope.firewallsLabel = FirewallLabels.get('Firewalls');

    $scope.title = title;

    $scope.applicationName = application.name;
    $scope.application = application;

    $scope.command = serverGroupCommand;

    $scope.state = {
      loaded: false,
      requiresTemplateSelection: !!serverGroupCommand.viewState.requiresTemplateSelection,
    };

    this.templateSelectionText = {
      copied: [
        'account, region, subnet, cluster name (stack, details)',
        'load balancers',
        FirewallLabels.get('firewalls'),
        'instance type',
        'all fields on the Advanced Settings page',
      ],
      notCopied: [],
    };

    if (!$scope.command.viewState.disableStrategySelection) {
      this.templateSelectionText.notCopied.push(
        'the deployment strategy (if any) used to deploy the most recent server group',
      );
    }

    function onApplicationRefresh() {
      // If the user has already closed the modal, do not navigate to the new details view
      if ($scope.$$destroyed) {
        return;
      }
      const cloneStage = $scope.taskMonitor.task.execution.stages.find(stage => stage.type === 'cloneServerGroup');
      if (cloneStage && cloneStage.context['deploy.server.groups']) {
        const newServerGroupName = cloneStage.context['deploy.server.groups'][$scope.command.region];
        if (newServerGroupName) {
          const newStateParams = {
            serverGroup: newServerGroupName,
            accountId: $scope.command.credentials,
            region: $scope.command.region,
            provider: 'gce',
          };
          let transitionTo = '^.^.^.clusters.serverGroup';
          if ($state.includes('**.clusters.serverGroup')) {
            // clone via details, all view
            transitionTo = '^.serverGroup';
          }
          if ($state.includes('**.clusters.cluster.serverGroup')) {
            // clone or create with details open
            transitionTo = '^.^.serverGroup';
          }
          if ($state.includes('**.clusters')) {
            // create new, no details open
            transitionTo = '.serverGroup';
          }
          $state.go(transitionTo, newStateParams);
        }
      }
    }

    function onTaskComplete() {
      application.serverGroups.refresh();
      application.serverGroups.onNextRefresh($scope, onApplicationRefresh);
    }

    $scope.taskMonitor = new TaskMonitor({
      application: application,
      title: 'Creating your server group',
      modalInstance: $uibModalInstance,
      onTaskComplete: onTaskComplete,
    });

    function configureCommand() {
      gceServerGroupConfigurationService
        .configureCommand(application, serverGroupCommand)
        .then(function() {
          const mode = serverGroupCommand.viewState.mode;
          if (mode === 'clone' || mode === 'create') {
            if (!serverGroupCommand.backingData.packageImages || !serverGroupCommand.backingData.packageImages.length) {
              serverGroupCommand.viewState.useAllImageSelection = true;
            }
          }
          $scope.state.loaded = true;
          initializeSelectOptions();
          initializeWatches();
          wizardSubFormValidation
            .config({ scope: $scope, form: 'form' })
            .register({ page: 'location', subForm: 'basicSettings' })
            .register({ page: 'capacity', subForm: 'capacitySubForm' })
            .register({ page: 'zones', subForm: 'zonesSubForm' })
            .register({ page: 'load-balancers', subForm: 'loadBalancerSubForm' })
            .register({ page: 'autohealing-policy', subForm: 'autoHealingPolicySubForm' });
        })
        .catch(e => {
          $log.error('Error generating server group command: ', e);
        });
    }

    function initializeWatches() {
      $scope.$watch('command.credentials', createResultProcessor($scope.command.credentialsChanged));
      $scope.$watch('command.regional', createResultProcessor($scope.command.regionalChanged));
      $scope.$watch('command.region', createResultProcessor($scope.command.regionChanged));
      $scope.$watch('command.network', createResultProcessor($scope.command.networkChanged));
      $scope.$watch('command.zone', createResultProcessor($scope.command.zoneChanged));
      $scope.$watch('command.viewState.instanceTypeDetails', updateStorageSettingsFromInstanceType());
      $scope.$watch(
        'command.viewState.customInstance',
        () => {
          $scope.command.customInstanceChanged($scope.command);
          setInstanceTypeFromCustomChoices();
        },
        true,
      );
    }

    function initializeSelectOptions() {
      processCommandUpdateResult($scope.command.credentialsChanged($scope.command));
      processCommandUpdateResult($scope.command.regionalChanged($scope.command));
      processCommandUpdateResult($scope.command.regionChanged($scope.command));
      processCommandUpdateResult($scope.command.networkChanged($scope.command));
      processCommandUpdateResult($scope.command.zoneChanged($scope.command));
      processCommandUpdateResult($scope.command.customInstanceChanged($scope.command));
      gceServerGroupConfigurationService.configureSubnets($scope.command);
    }

    function createResultProcessor(method) {
      return function() {
        processCommandUpdateResult(method($scope.command));
      };
    }

    function processCommandUpdateResult(result) {
      if (result.dirty.loadBalancers) {
        ModalWizard.markDirty('load-balancers');
      }
      if (result.dirty.securityGroups) {
        ModalWizard.markDirty('security-groups');
      }
      if (result.dirty.availabilityZones) {
        ModalWizard.markDirty('capacity');
      }
      if (result.dirty.instanceType) {
        ModalWizard.markDirty('instance-type');
      }
    }

    function setInstanceTypeFromCustomChoices() {
      const c = $scope.command,
        location = c.regional ? c.region : c.zone,
        { locationToInstanceTypesMap } = c.backingData.credentialsKeyedByAccount[c.credentials];

      const customInstanceChoices = [
        _.get(c, 'viewState.customInstance.vCpuCount'),
        _.get(c, 'viewState.customInstance.memory'),
      ];

      if (
        _.every([
          ...customInstanceChoices,
          gceCustomInstanceBuilderService.customInstanceChoicesAreValid(
            ...customInstanceChoices,
            location,
            locationToInstanceTypesMap,
          ),
        ])
      ) {
        c.instanceType = gceCustomInstanceBuilderService.generateInstanceTypeString(...customInstanceChoices);

        instanceTypeService.getInstanceTypeDetails(c.selectedProvider, 'buildCustom').then(instanceTypeDetails => {
          c.viewState.instanceTypeDetails = instanceTypeDetails;
        });
      }
    }

    function updateStorageSettingsFromInstanceType() {
      return function(instanceTypeDetails) {
        if ($scope.command.viewState.initialized) {
          if (instanceTypeDetails && instanceTypeDetails.storage && instanceTypeDetails.storage.defaultSettings) {
            $scope.command.disks = instanceTypeDetails.storage.defaultSettings.disks;
            delete $scope.command.viewState.overriddenStorageDescription;
          }
        } else {
          $scope.command.viewState.initialized = true;
        }
      };
    }

    this.isValid = function() {
      const selectedZones = $scope.command.selectZones && _.get($scope, 'command.distributionPolicy.zones.length') >= 1;
      return (
        $scope.command &&
        ($scope.command.viewState.disableImageSelection || $scope.command.image) &&
        $scope.command.application &&
        $scope.command.credentials &&
        $scope.command.instanceType &&
        $scope.command.region &&
        ($scope.command.regional || $scope.command.zone) &&
        $scope.command.capacity.desired !== null &&
        (!$scope.command.selectZones || selectedZones) &&
        $scope.form.$valid &&
        ModalWizard.isComplete()
      );
    };

    this.showSubmitButton = function() {
      return ModalWizard.allPagesVisited();
    };

    function buildLoadBalancerMetadata(loadBalancerNames, loadBalancerIndex, backendServices) {
      let metadata = {};

      if (_.get(loadBalancerNames, 'length') > 0) {
        metadata = loadBalancerNames.reduce(
          (metadata, name) => {
            const loadBalancerDetails = loadBalancerIndex[name];

            if (loadBalancerDetails.loadBalancerType === 'HTTP') {
              metadata['global-load-balancer-names'] = metadata['global-load-balancer-names'].concat(
                loadBalancerDetails.listeners.map(listener => listener.name),
              );
            } else if (loadBalancerDetails.loadBalancerType === 'SSL') {
              metadata['global-load-balancer-names'].push(name);
            } else if (loadBalancerDetails.loadBalancerType === 'TCP') {
              metadata['global-load-balancer-names'].push(name);
            } else {
              metadata['load-balancer-names'].push(name);
            }
            return metadata;
          },
          { 'load-balancer-names': [], 'global-load-balancer-names': [] },
        );
      }

      if (_.isObject(backendServices) && Object.keys(backendServices).length > 0) {
        metadata['backend-service-names'] = _.reduce(
          backendServices,
          (accumulatedBackends, backends) => accumulatedBackends.concat(backends),
          [],
        );
      }

      for (const key in metadata) {
        if (metadata[key].length === 0) {
          delete metadata[key];
        } else {
          metadata[key] = _.uniq(metadata[key]).toString();
        }
      }

      return metadata;
    }

    function collectLoadBalancerNamesForCommand(loadBalancerIndex, loadBalancerMetadata) {
      let loadBalancerNames = [];
      if (loadBalancerMetadata['load-balancer-names']) {
        loadBalancerNames = loadBalancerNames.concat(loadBalancerMetadata['load-balancer-names'].split(','));
      }

      const selectedSslLoadBalancerNames = _.chain(loadBalancerIndex)
        .filter({ loadBalancerType: 'SSL' })
        .map('name')
        .intersection(
          loadBalancerMetadata['global-load-balancer-names']
            ? loadBalancerMetadata['global-load-balancer-names'].split(',')
            : [],
        )
        .value();

      const selectedTcpLoadBalancerNames = _.chain(loadBalancerIndex)
        .filter({ loadBalancerType: 'TCP' })
        .map('name')
        .intersection(
          loadBalancerMetadata['global-load-balancer-names']
            ? loadBalancerMetadata['global-load-balancer-names'].split(',')
            : [],
        )
        .value();

      return loadBalancerNames.concat(selectedSslLoadBalancerNames).concat(selectedTcpLoadBalancerNames);
    }

    this.submit = function() {
      // We use this list of load balancer names when 'Enabling' a server group.
      const loadBalancerMetadata = buildLoadBalancerMetadata(
        $scope.command.loadBalancers,
        $scope.command.backingData.filtered.loadBalancerIndex,
        $scope.command.backendServices,
      );

      const origLoadBalancers = $scope.command.loadBalancers;
      $scope.command.loadBalancers = collectLoadBalancerNamesForCommand(
        $scope.command.backingData.filtered.loadBalancerIndex,
        loadBalancerMetadata,
      );

      if ($scope.command.minCpuPlatform === '(Automatic)') {
        $scope.command.minCpuPlatform = '';
      }

      angular.extend($scope.command.instanceMetadata, loadBalancerMetadata);

      const origTags = $scope.command.tags;
      const transformedTags = [];
      // The tags are stored using a 'value' attribute to enable the Add/Remove behavior in the wizard.
      $scope.command.tags.forEach(function(tag) {
        transformedTags.push(tag.value);
      });
      $scope.command.tags = transformedTags;

      $scope.command.targetSize = $scope.command.capacity.desired;

      if ($scope.command.autoscalingPolicy) {
        $scope.command.capacity.min = $scope.command.autoscalingPolicy.minNumReplicas;
        $scope.command.capacity.max = $scope.command.autoscalingPolicy.maxNumReplicas;
      } else {
        $scope.command.capacity.min = $scope.command.capacity.desired;
        $scope.command.capacity.max = $scope.command.capacity.desired;
      }

      delete $scope.command.securityGroups;

      if ($scope.command.viewState.mode === 'editPipeline' || $scope.command.viewState.mode === 'createPipeline') {
        return $uibModalInstance.close($scope.command);
      }

      const healthCheckUrl = _.get($scope.command, 'autoHealingPolicy.healthCheck');
      if (healthCheckUrl) {
        const { healthCheckName, healthCheckKind } = parseHealthCheckUrl(healthCheckUrl);
        $scope.command.autoHealingPolicy.healthCheck = healthCheckName;
        $scope.command.autoHealingPolicy.healthCheckKind = healthCheckKind;
      }

      $scope.taskMonitor.submit(function() {
        const promise = serverGroupWriter.cloneServerGroup(angular.copy($scope.command), application);

        // Copy back the original objects so the wizard can still be used if the command needs to be resubmitted.
        $scope.command.instanceMetadata = _.omit($scope.command.instanceMetadata, gceServerGroupHiddenMetadataKeys);

        $scope.command.tags = origTags;
        $scope.command.loadBalancers = origLoadBalancers;
        $scope.command.securityGroups = gceTagManager.inferSecurityGroupIdsFromTags($scope.command.tags);

        if (healthCheckUrl) {
          $scope.command.autoHealingPolicy.healthCheck = healthCheckUrl;
        }

        return promise;
      });
    };

    this.onHealthCheckRefresh = function() {
      gceServerGroupConfigurationService.refreshHealthChecks($scope.command);
    };

    this.onEnableAutoHealingChange = function() {
      // Prevent empty auto-healing policies from being overwritten by those of their ancestors
      $scope.command.overwriteAncestorAutoHealingPolicy =
        $scope.command.viewState.mode === 'clone' &&
        $scope.command.autoHealingPolicy != null &&
        $scope.command.enableAutoHealing === false;
    };

    this.setAutoHealingPolicy = function(autoHealingPolicy) {
      $scope.command.autoHealingPolicy = autoHealingPolicy;
    };

    this.cancel = function() {
      $uibModalInstance.dismiss();
    };

    this.specialInstanceProfiles = new Set(['custom', 'buildCustom']);

    // This function is called from within React, and without $apply, Angular does not know when it has been called.
    $scope.command.setCustomInstanceViewState = customInstanceChoices => {
      $scope.$apply(() => ($scope.command.viewState.customInstance = customInstanceChoices));
    };

    if (!$scope.state.requiresTemplateSelection) {
      configureCommand();
    } else {
      $scope.state.loaded = true;
    }

    this.templateSelected = () => {
      $scope.state.requiresTemplateSelection = false;
      configureCommand();
    };

    $scope.$on('$destroy', gceTagManager.reset);
  });
<|endoftext|>


<commit_before>import React from 'react'
import injectSheet from 'react-jss'
import classnames from 'classnames'

import { Phone, Desktop } from '../../components/breakpoints'

const Button = ({ classes, onClick, buttonText }) => {
	return (

				<Phone>

						<button
							className={classnames(classes.buttonCommon, classes.buttonPhone)}
							type="submit"
							onClick={onClick}
						>
							{buttonText}
						</button>

				</Phone>

				<Desktop>

						<button
							className={classnames(
								classes.buttonCommon,
								classes.buttonDesktop,
							)}
							type="submit"
							onClick={onClick}
						>
							{buttonText}
						</button>

				</Desktop>

	)
}

const styles = {
	// goButtonRoot: {
	// 	display: 'flex',
	// 	'justify-content': 'center',
	// },
	buttonCommon: {
		'background-color': '#FFFFFF',
		'border-radius': '8px',
		color: '#000000',
		'font-family': 'hah-regular',
		'text-align': 'center',
		cursor: 'pointer',
		'text-decoration': 'none',
		outline: 'none',
		'&:hover': {
			'background-color': '#3D65F9',
			color: '#FFFFFF',
		},
	},
	buttonPhone: {
		border: '1px solid #3D65F9',
		'font-size': '14px',
		height: '31px',
		width: '96px',
	},
	buttonDesktop: {
		border: '2px solid #3D65F9',
		'font-size': '24px',
		'letter-spacing': '0.5px',
		height: '54px',
		width: '186px',
	},
}

export default injectSheet(styles)(Button)
<commit_msg>Remove commented out code<commit_after>import React from 'react'
import injectSheet from 'react-jss'
import classnames from 'classnames'

import { Phone, Desktop } from '../../components/breakpoints'

const Button = ({ classes, onClick, buttonText }) => {
	return (

				<Phone>

						<button
							className={classnames(classes.buttonCommon, classes.buttonPhone)}
							type="submit"
							onClick={onClick}
						>
							{buttonText}
						</button>

				</Phone>

				<Desktop>

						<button
							className={classnames(
								classes.buttonCommon,
								classes.buttonDesktop,
							)}
							type="submit"
							onClick={onClick}
						>
							{buttonText}
						</button>

				</Desktop>

	)
}

const styles = {
	buttonCommon: {
		'background-color': '#FFFFFF',
		'border-radius': '8px',
		color: '#000000',
		'font-family': 'hah-regular',
		'text-align': 'center',
		cursor: 'pointer',
		'text-decoration': 'none',
		outline: 'none',
		'&:hover': {
			'background-color': '#3D65F9',
			color: '#FFFFFF',
		},
	},
	buttonPhone: {
		border: '1px solid #3D65F9',
		'font-size': '14px',
		height: '31px',
		width: '96px',
	},
	buttonDesktop: {
		border: '2px solid #3D65F9',
		'font-size': '24px',
		'letter-spacing': '0.5px',
		height: '54px',
		width: '186px',
	},
}

export default injectSheet(styles)(Button)
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';

class Counter extends Component {
  constructor() {
    super();
    this.state = { counter: 0 };

    this.increment = this.increment.bind(this);
    this.decrement = this.decrement.bind(this);
  }

  increment() {
    const state = { counter: this.state.counter + 1 };
    window.devToolsExtension && window.devToolsExtension.send('increment', state);
    this.setState(state);
  }

  decrement() {
    const state = { counter: this.state.counter - 1 };
    window.devToolsExtension && window.devToolsExtension.send('decrement', state);
    this.setState(state);
  }

  render() {
    const { counter } = this.state;
    return (

        Clicked: {counter} times
        {' '}
        <button onClick={this.increment}>+</button>
        {' '}
        <button onClick={this.decrement}>-</button>

    );
  }
}

export default Counter;
<commit_msg>Revert "Send changes in react-counter-messaging"<commit_after>import React, { Component, PropTypes } from 'react';

class Counter extends Component {
  constructor() {
    super();
    this.state = { counter: 0 };

    this.increment = this.increment.bind(this);
    this.decrement = this.decrement.bind(this);
  }

  increment() {
    const state = { counter: this.state.counter + 1 };
    this.setState(state);
  }

  decrement() {
    const state = { counter: this.state.counter - 1 };
    this.setState(state);
  }

  render() {
    const { counter } = this.state;
    return (

        Clicked: {counter} times
        {' '}
        <button onClick={this.increment}>+</button>
        {' '}
        <button onClick={this.decrement}>-</button>

    );
  }
}

export default Counter;
<|endoftext|>


<commit_before>// Shipments AJAX API
$(document).ready(function () {
  'use strict';

  // handle variant selection, show stock level.
  $('#add_variant_id').change(function(){
    var variant_id = $(this).val();

    var variant = _.find(window.variants, function(variant){
      return variant.id == variant_id
    })

    var variantStockTemplate = HandlebarsTemplates["variants/autocomplete_stock"];
    $('#stock_details').html(variantStockTemplate({variant: variant}));
    $('#stock_details').show();

    $('button.add_variant').click(addVariantFromStockLocation);
  });

  // add header to ship ujs ajax call
  $("form#admin-ship-shipment").on("ajax:beforeSend", function(event, xhr, settings) {
    xhr.setRequestHeader("X-Spree-Token", Spree.api_key);
  });

  $("form#admin-ship-shipment").on("ajax:success", function(event, xhr, settings) {
    window.location.reload();
  });
});

updateShipment = function(shipment_number, attributes) {
  var url = Spree.routes.shipments_api + '/' + shipment_number + '.json';

  return Spree.ajax({
    type: 'PUT',
    url: url,
    data: {
      shipment: attributes
    }
  });
}

adjustShipmentItems = function(shipment_number, variant_id, quantity){
    var shipment = _.findWhere(shipments, {number: shipment_number + ''});
    var inventory_units = _.where(shipment.inventory_units, {variant_id: variant_id});

    var url = Spree.routes.shipments_api + "/" + shipment_number;

    var new_quantity = 0;
    if(inventory_units.length<quantity){
      url += "/add"
      new_quantity = (quantity - inventory_units.length);
    }else if(inventory_units.length>quantity){
      url += "/remove"
      new_quantity = (inventory_units.length - quantity);
    }
    url += '.json';

    if(new_quantity!=0){
      Spree.ajax({
        type: "PUT",
        url: url,
        data: {
          variant_id: variant_id,
          quantity: new_quantity,
        },
        success: function(response) {
          window.location.reload();
        },
        error: function(response) {
          show_flash('error', response.responseJSON.message);
        }
      });
    }
}

deleteLineItem = function(line_item_id){
  var url = Spree.routes.line_items_api(order_number) + "/" + line_item_id + ".json";

  Spree.ajax({
    type: "DELETE",
    url: url,
    success: function(response) {
      window.location.reload();
    },
    error: function(response) {
      show_flash('error', response.responseJSON.message);
    }
  });
}

startItemSplit = function(event){
  event.preventDefault();
  var link = $(this);
  link.parent().find('a.split-item').toggle();
  link.parent().find('a.delete-item').toggle();
  var variant_id = link.data('variant-id');

  Spree.ajax({
    type: "GET",
    url: Spree.routes.variants_api + "/" + variant_id,
  }).success(function(variant){
    var max_quantity = link.closest('tr').data('item-quantity');
    var split_item_template = HandlebarsTemplates['variants/split'];
    link.closest('tr').after(split_item_template({ variant: variant, shipments: shipments, max_quantity: max_quantity }));

    $('#item_stock_location').select2({ width: 'resolve', placeholder: Spree.translations.item_stock_placeholder });
  })
}

completeItemSplit = function(event) {
  event.preventDefault();

  if($('#item_stock_location').val() === ""){
      alert('Please select the split destination.');
      return false;
  }

  var link = $(this);
  var stock_item_row = link.closest('tr');
  var variant_id = stock_item_row.data('variant-id');
  var quantity = stock_item_row.find('#item_quantity').val();

  var stock_location_id = stock_item_row.find('#item_stock_location').val();
  var original_shipment_number = link.closest('tbody').data('shipment-number');

  var selected_shipment = stock_item_row.find($('#item_stock_location').select2('data').element);
  var target_shipment_number = selected_shipment.data('shipment-number');
  var new_shipment = selected_shipment.data('new-shipment');

  if (stock_location_id != 'new_shipment') {
    if (new_shipment != undefined) {
      // TRANSFER TO A NEW LOCATION
      Spree.ajax({
        type: "POST",
        async: false,
        url: Spree.routes.shipments_api + "/transfer_to_location",
        data: {
            original_shipment_number: original_shipment_number,
            variant_id: variant_id,
            quantity: quantity,
            stock_location_id: stock_location_id
        }
      }).error(function(msg) {
          alert(msg.responseJSON['message']);
      }).done(function(msg) {
        window.Spree.advanceOrder();
      });
    } else {
        // TRANSFER TO AN EXISTING SHIPMENT
        Spree.ajax({
            type: "POST",
            async: false,
            url: Spree.routes.shipments_api + "/transfer_to_shipment",
            data: {
                original_shipment_number: original_shipment_number,
                target_shipment_number: target_shipment_number,
                variant_id: variant_id,
                quantity: quantity
            }
        }).error(function(msg) {
            alert(msg.responseJSON['message']);
        }).done(function(msg) {
            window.Spree.advanceOrder();
        });
    }
  }
}

addVariantFromStockLocation = function(event) {
  event.preventDefault();

  $('#stock_details').hide();

  var variant_id = $('input.variant_autocomplete').val();
  var stock_location_id = $(this).data('stock-location-id');
  var quantity = $("input.quantity[data-stock-location-id='" + stock_location_id + "']").val();

  var shipment = _.find(shipments, function(shipment){
    return shipment.stock_location_id == stock_location_id && (shipment.state == 'ready' || shipment.state == 'pending');
  });

  if(shipment==undefined){
    Spree.ajax({
      type: "POST",
      url: Spree.routes.shipments_api + "?shipment[order_id]=" + order_number,
      data: {
        variant_id: variant_id,
        quantity: quantity,
        stock_location_id: stock_location_id,
      }
    }).done(function( msg ) {
      window.location.reload();
    });
  }else{
    //add to existing shipment
    adjustShipmentItems(shipment.number, variant_id, quantity);
  }
  return 1
}

var ShipmentEditView = Backbone.View.extend({
  events: {
    "click a.split-item": "startItemSplit",
    "click a.edit-method": "toggleMethodEdit",
    "click a.cancel-method": "toggleMethodEdit",
    "click a.delete-item": "deleteItem",
    "click a.edit-tracking": "toggleTrackingEdit",
    "click a.cancel-tracking": "toggleTrackingEdit",
    "click a.cancel-split": "cancelItemSplit",
    "click a.save-split": "completeItemSplit",
    "click a.save-method": "saveMethod",
    "click a.save-tracking": "saveTracking",
  },

  startItemSplit: function(e){
    startItemSplit.apply(e.currentTarget, [e]);
  },

  toggleMethodEdit: function(){
    this.$('tr.edit-method').toggle();
    this.$('tr.show-method').toggle();
  },

  cancelItemSplit: function(e){
    var link = $(e.currentTarget);
    var prev_row = link.closest('tr').prev();
    link.closest('tr').remove();
    prev_row.find('a.split-item').show();
    prev_row.find('a.delete-item').show();
  },

  completeItemSplit: function(e){
    completeItemSplit.apply(e.currentTarget, [e]);
  },

  deleteItem: function(e){
    if (confirm(Spree.translations.are_you_sure_delete)) {
      var del = $(e.currentTarget);
      var line_item_id = del.data('line-item-id');

      deleteLineItem(line_item_id);
    }
  },

  toggleTrackingEdit: function() {
    this.$("tr.edit-tracking").toggle()
    this.$("tr.show-tracking").toggle()
  },

  saveMethod: function(e) {
    var link = $(e.currentTarget);
    var shipment_number = link.data('shipment-number');
    var selected_shipping_rate_id = link.parents('tbody').find("select#selected_shipping_rate_id[data-shipment-number='" + shipment_number + "']").val();
    updateShipment(shipment_number, {
      selected_shipping_rate_id: selected_shipping_rate_id
    }).done(function () {
      window.location.reload();
    });
  },

  // handle tracking save
  saveTracking: function (e) {
    var link = $(e.currentTarget);
    var shipment_number = link.data('shipment-number');
    var tracking = link.parents('tbody').find('input#tracking').val();

    updateShipment(shipment_number, {tracking: tracking}).done(function (data) {
      link.parents('tbody').find('tr.edit-tracking').toggle();

      var show = link.parents('tbody').find('tr.show-tracking');
      show.toggle();
      show.find('.tracking-value').html($("<strong>").html(Spree.translations.tracking + ": ")).append(data.tracking);
    });
  }
});

$(function(){
  $(".js-shipment-edit").each(function(){
    new ShipmentEditView({ el: $(this) });
  });
});
<commit_msg>Find shipment and order number on initialize<commit_after>// Shipments AJAX API
$(document).ready(function () {
  'use strict';

  // handle variant selection, show stock level.
  $('#add_variant_id').change(function(){
    var variant_id = $(this).val();

    var variant = _.find(window.variants, function(variant){
      return variant.id == variant_id
    })

    var variantStockTemplate = HandlebarsTemplates["variants/autocomplete_stock"];
    $('#stock_details').html(variantStockTemplate({variant: variant}));
    $('#stock_details').show();

    $('button.add_variant').click(addVariantFromStockLocation);
  });

  // add header to ship ujs ajax call
  $("form#admin-ship-shipment").on("ajax:beforeSend", function(event, xhr, settings) {
    xhr.setRequestHeader("X-Spree-Token", Spree.api_key);
  });

  $("form#admin-ship-shipment").on("ajax:success", function(event, xhr, settings) {
    window.location.reload();
  });
});

updateShipment = function(shipment_number, attributes) {
  var url = Spree.routes.shipments_api + '/' + shipment_number + '.json';

  return Spree.ajax({
    type: 'PUT',
    url: url,
    data: {
      shipment: attributes
    }
  });
}

adjustShipmentItems = function(shipment_number, variant_id, quantity){
    var shipment = _.findWhere(shipments, {number: shipment_number + ''});
    var inventory_units = _.where(shipment.inventory_units, {variant_id: variant_id});

    var url = Spree.routes.shipments_api + "/" + shipment_number;

    var new_quantity = 0;
    if(inventory_units.length<quantity){
      url += "/add"
      new_quantity = (quantity - inventory_units.length);
    }else if(inventory_units.length>quantity){
      url += "/remove"
      new_quantity = (inventory_units.length - quantity);
    }
    url += '.json';

    if(new_quantity!=0){
      Spree.ajax({
        type: "PUT",
        url: url,
        data: {
          variant_id: variant_id,
          quantity: new_quantity,
        },
        success: function(response) {
          window.location.reload();
        },
        error: function(response) {
          show_flash('error', response.responseJSON.message);
        }
      });
    }
}

deleteLineItem = function(line_item_id){
  var url = Spree.routes.line_items_api(order_number) + "/" + line_item_id + ".json";

  Spree.ajax({
    type: "DELETE",
    url: url,
    success: function(response) {
      window.location.reload();
    },
    error: function(response) {
      show_flash('error', response.responseJSON.message);
    }
  });
}

startItemSplit = function(event){
  event.preventDefault();
  var link = $(this);
  link.parent().find('a.split-item').toggle();
  link.parent().find('a.delete-item').toggle();
  var variant_id = link.data('variant-id');

  Spree.ajax({
    type: "GET",
    url: Spree.routes.variants_api + "/" + variant_id,
  }).success(function(variant){
    var max_quantity = link.closest('tr').data('item-quantity');
    var split_item_template = HandlebarsTemplates['variants/split'];
    link.closest('tr').after(split_item_template({ variant: variant, shipments: shipments, max_quantity: max_quantity }));

    $('#item_stock_location').select2({ width: 'resolve', placeholder: Spree.translations.item_stock_placeholder });
  })
}

completeItemSplit = function(event) {
  event.preventDefault();

  if($('#item_stock_location').val() === ""){
      alert('Please select the split destination.');
      return false;
  }

  var link = $(this);
  var stock_item_row = link.closest('tr');
  var variant_id = stock_item_row.data('variant-id');
  var quantity = stock_item_row.find('#item_quantity').val();

  var stock_location_id = stock_item_row.find('#item_stock_location').val();
  var original_shipment_number = link.closest('tbody').data('shipment-number');

  var selected_shipment = stock_item_row.find($('#item_stock_location').select2('data').element);
  var target_shipment_number = selected_shipment.data('shipment-number');
  var new_shipment = selected_shipment.data('new-shipment');

  if (stock_location_id != 'new_shipment') {
    if (new_shipment != undefined) {
      // TRANSFER TO A NEW LOCATION
      Spree.ajax({
        type: "POST",
        async: false,
        url: Spree.routes.shipments_api + "/transfer_to_location",
        data: {
            original_shipment_number: original_shipment_number,
            variant_id: variant_id,
            quantity: quantity,
            stock_location_id: stock_location_id
        }
      }).error(function(msg) {
          alert(msg.responseJSON['message']);
      }).done(function(msg) {
        window.Spree.advanceOrder();
      });
    } else {
        // TRANSFER TO AN EXISTING SHIPMENT
        Spree.ajax({
            type: "POST",
            async: false,
            url: Spree.routes.shipments_api + "/transfer_to_shipment",
            data: {
                original_shipment_number: original_shipment_number,
                target_shipment_number: target_shipment_number,
                variant_id: variant_id,
                quantity: quantity
            }
        }).error(function(msg) {
            alert(msg.responseJSON['message']);
        }).done(function(msg) {
            window.Spree.advanceOrder();
        });
    }
  }
}

addVariantFromStockLocation = function(event) {
  event.preventDefault();

  $('#stock_details').hide();

  var variant_id = $('input.variant_autocomplete').val();
  var stock_location_id = $(this).data('stock-location-id');
  var quantity = $("input.quantity[data-stock-location-id='" + stock_location_id + "']").val();

  var shipment = _.find(shipments, function(shipment){
    return shipment.stock_location_id == stock_location_id && (shipment.state == 'ready' || shipment.state == 'pending');
  });

  if(shipment==undefined){
    Spree.ajax({
      type: "POST",
      url: Spree.routes.shipments_api + "?shipment[order_id]=" + order_number,
      data: {
        variant_id: variant_id,
        quantity: quantity,
        stock_location_id: stock_location_id,
      }
    }).done(function( msg ) {
      window.location.reload();
    });
  }else{
    //add to existing shipment
    adjustShipmentItems(shipment.number, variant_id, quantity);
  }
  return 1
}

var ShipmentEditView = Backbone.View.extend({
  initialize: function(){
    var tbody = this.$("tbody[data-order-number][data-shipment-number]");
    this.shipment_number = tbody.data("shipment-number")
    this.order_number = tbody.data("order-number")
  },

  events: {
    "click a.split-item": "startItemSplit",
    "click a.edit-method": "toggleMethodEdit",
    "click a.cancel-method": "toggleMethodEdit",
    "click a.delete-item": "deleteItem",
    "click a.edit-tracking": "toggleTrackingEdit",
    "click a.cancel-tracking": "toggleTrackingEdit",
    "click a.cancel-split": "cancelItemSplit",
    "click a.save-split": "completeItemSplit",
    "click a.save-method": "saveMethod",
    "click a.save-tracking": "saveTracking",
  },

  startItemSplit: function(e){
    startItemSplit.apply(e.currentTarget, [e]);
  },

  toggleMethodEdit: function(){
    this.$('tr.edit-method').toggle();
    this.$('tr.show-method').toggle();
  },

  cancelItemSplit: function(e){
    var link = $(e.currentTarget);
    var prev_row = link.closest('tr').prev();
    link.closest('tr').remove();
    prev_row.find('a.split-item').show();
    prev_row.find('a.delete-item').show();
  },

  completeItemSplit: function(e){
    completeItemSplit.apply(e.currentTarget, [e]);
  },

  deleteItem: function(e){
    if (confirm(Spree.translations.are_you_sure_delete)) {
      var del = $(e.currentTarget);
      var line_item_id = del.data('line-item-id');

      deleteLineItem(line_item_id);
    }
  },

  toggleTrackingEdit: function() {
    this.$("tr.edit-tracking").toggle()
    this.$("tr.show-tracking").toggle()
  },

  saveMethod: function(e) {
    var link = $(e.currentTarget);
    var shipment_number = link.data('shipment-number');
    var selected_shipping_rate_id = link.parents('tbody').find("select#selected_shipping_rate_id[data-shipment-number='" + shipment_number + "']").val();
    updateShipment(shipment_number, {
      selected_shipping_rate_id: selected_shipping_rate_id
    }).done(function () {
      window.location.reload();
    });
  },

  // handle tracking save
  saveTracking: function (e) {
    var link = $(e.currentTarget);
    var shipment_number = link.data('shipment-number');
    var tracking = link.parents('tbody').find('input#tracking').val();

    updateShipment(shipment_number, {tracking: tracking}).done(function (data) {
      link.parents('tbody').find('tr.edit-tracking').toggle();

      var show = link.parents('tbody').find('tr.show-tracking');
      show.toggle();
      show.find('.tracking-value').html($("<strong>").html(Spree.translations.tracking + ": ")).append(data.tracking);
    });
  }
});

$(function(){
  $(".js-shipment-edit").each(function(){
    new ShipmentEditView({ el: $(this) });
  });
});
<|endoftext|>


<commit_before>export default {
    "$schema": "http://json-schema.org/draft-04/schema#",
    "definitions": {
        "alarm": {
            "type": "object",
            "properties": {
                "userId": {
                    "type": "string"
                },
                "sensorId": {
                    "type": "string"
                },
                "rule": {
                    "type": "string"
                },
                "type": {
                    "type": "string",
                    "enum": ["realtime", "daily", "monthly"]
                },
                "thresholdRule": {
                    "type": "string"
                },
                "threshold": {
                    "type": "number"
                },
                "unitOfMeasurement": {
                    "type": "string"
                }
            },
            "additionalProperties": false,
            "required": [
                "userId",
                "sensorId",
                "rule",
                "type",
                "threshold",
                "thresholdRule",
                "unitOfMeasurement"
            ]
        }
    }
};
<commit_msg>Added measurementType to alarms' schema<commit_after>export default {
    "$schema": "http://json-schema.org/draft-04/schema#",
    "definitions": {
        "alarm": {
            "type": "object",
            "properties": {
                "userId": {
                    "type": "string"
                },
                "sensorId": {
                    "type": "string"
                },
                "rule": {
                    "type": "string"
                },
                "type": {
                    "type": "string",
                    "enum": ["realtime", "daily", "monthly"]
                },
                "thresholdRule": {
                    "type": "string"
                },
                "threshold": {
                    "type": "number"
                },
                "unitOfMeasurement": {
                    "type": "string"
                },
                "measurementType": {
                    "type": "string"
                }
            },
            "additionalProperties": false,
            "required": [
                "userId",
                "sensorId",
                "rule",
                "type",
                "threshold",
                "thresholdRule",
                "unitOfMeasurement",
                "measurementType"
            ]
        }
    }
};
<|endoftext|>


<commit_before>
function foo(a, b)
{
  console.log (a);
  if (b == null)
    console.log ("that's all");
}

foo("hello world");
<commit_msg>add a test with both args supplied<commit_after>
function foo(a, b)
{
  console.log (a);
  if (b == null)
    console.log ("that's all");
}

foo("hello world");
foo("hello world", "goodbye world");
<|endoftext|>


<commit_before>/**
 * Developer: Stepan Burguchev
 * Date: 8/07/2015
 * Copyright: 2009-2015 Comindware®
 *       All Rights Reserved
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF Comindware
 *       The copyright notice above does not evidence any
 *       actual or intended publication of such source code.
 */

/* global define, require, Handlebars, Backbone, Marionette, $, _, Localizer */

define([ 'core/utils/utilsApi'],
    function (utilsApi) {
        'use strict';

        var globalPermissions = {
            SYSTEM_ADMINISTRATION: 'system_administration',
            USER_MANAGEMENT: 'user_management',
            APP_DESIGN: 'app_design',
            DEFAULT_USER: 'default_user'
        };

        return {
            initialize: function (options) {
            },

            hasGlobalPermission: function (permissionId) {
                return Context.configurationModel.GlobalPermissions[0] === permissionId;
            },

            globalPermissions: globalPermissions
        };
    });
<commit_msg>Review CP-CR-120 corrections<commit_after>/**
 * Developer: Stepan Burguchev
 * Date: 8/07/2015
 * Copyright: 2009-2015 Comindware®
 *       All Rights Reserved
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF Comindware
 *       The copyright notice above does not evidence any
 *       actual or intended publication of such source code.
 */

/* global define, require, Handlebars, Backbone, Marionette, $, _, Localizer */

define([ 'core/utils/utilsApi'],
    function (utilsApi) {
        'use strict';

        var globalPermissions = {
            SYSTEM_ADMINISTRATION: 'system_administration',
            USER_MANAGEMENT: 'user_management',
            APP_DESIGN: 'app_design',
            DEFAULT_USER: 'default_user'
        };

        return {
            initialize: function (options) {
            },

            hasGlobalPermission: function (permissionId) {
               var hasPermision = Context.configurationModel.GlobalPermissions.filter(function (permission) {
                    if (permission === permissionId) {
                        return true;
                    }
                });

               if (hasPermision.length > 0) {
                    return true;
                } else {
                    return false;
                }
            },

            globalPermissions: globalPermissions
        };
    });
<|endoftext|>


<commit_before>// Copyright 2015, EMC, Inc.
/* jshint node:true */

'use strict';

describe('Http.Api.Views', function () {
    var viewsProtocol;
    var _;

    before('start HTTP server', function () {
        this.timeout(10000);
        viewsProtocol = {
            load: sinon.stub(),
            getAll: sinon.stub(),
            get: sinon.stub(),
            put: sinon.stub(),
            unlink: sinon.stub()
        };

        return helper.startServer([
            dihelper.simpleWrapper(viewsProtocol, 'Views')
        ]).then(function() {
            _ = helper.injector.get('_');
        });
    });

    beforeEach('reset stubs', function () {
        _(viewsProtocol).methods().forEach(function (method) {
            if (_(viewsProtocol).has(method)) {
              viewsProtocol[method].reset();
            }
        }).value();
    });

    after('stop HTTP server', function () {
        return helper.stopServer();
    });

    describe('2.0 Views', function() {
        // should create a view (octet-stream)
        it('should get all views', function() {
            var views = [
                {name: 'foo', content: 'foo', scope: 'global'},
                {name: 'bar', content: 'bar', scope: 'global'}
            ];
            viewsProtocol.getAll.resolves(views);
            return helper.request().get('/api/2.0/views')
                .expect('Content-Type', /^application\/json/)
                .expect(200)
                .expect(function(res) {
                    res.body.forEach(function(view, i) {
                        expect(view).to.deep.equal(views[i]);
                    });
                    expect(viewsProtocol.getAll).to.have.been.calledOnce;
                });
        });

        it('should get one view', function() {
            var view = {name: 'foo', content: 'foo', scope: 'global'};

            viewsProtocol.get.resolves(view);
            return helper.request().get('/api/2.0/views/foo')
                .expect('Content-Type', /^application\/json/)
                .expect(200)
                .expect(function(res) {
                    expect(res.body).to.deep.equal(view);
                    expect(viewsProtocol.get).to.have.been.calledOnce;
                    expect(viewsProtocol.get).to.have.been.calledWith('foo');
                });
        });

        it('should fail to get non-existant view', function() {
            viewsProtocol.get.resolves();
            return helper.request().get('/api/2.0/views/foo')
                .expect('Content-Type', /^application\/json/)
                .expect(404)
                .expect(function() {
                    expect(viewsProtocol.get).to.have.been.calledOnce;
                    expect(viewsProtocol.get).to.have.been.calledWith('foo');
                });
        });

        it('should create a text/plain view', function() {
            var view = {name: 'foo', content: 'foo', scope: 'global'};

            viewsProtocol.put.resolves(view);
            return helper.request().put('/api/2.0/views/foo')
                .set('Content-Type', 'text/plain')
                .send('{ "message": "hello" }')
                .expect('Content-Type', /^application\/json/)
                .expect(200)
                .expect(function(res) {
                    expect(res.body).to.deep.equal(view);
                    expect(viewsProtocol.put).to.have.been.calledOnce;
                });
        });

        it('should create an application/octet-stream view', function() {
            var view = {name: 'foo', content: 'foo', scope: 'global'};
            var req = helper.request().put('/api/2.0/views/foo');

            viewsProtocol.put.resolves(view);
            req.set('Content-Type', 'application/octet-stream');
            req.write(new Buffer('{ "message": "hello" }', 'ascii'));
            req.end(function(err, res) {
                expect(res.get('Content-Type')).to.match(/^application\/json/);
                expect(res.status).to.equal(200);
                expect(res.body).to.deep.equal(view);
                expect(viewsProtocol.put).to.have.been.calledOnce;
            });
        });

        it('should reject invalid data type', function() {
            return helper.request().put('/api/2.0/views/foo')
                .set('Content-Type', 'text/html')
                .send('{ "message": "hello" }')
                .expect('Content-Type', /^application\/json/)
                .expect(400)
                .expect(function() {
                    expect(viewsProtocol.put).not.to.have.been.called;
                });
        });

        it('should delete a view', function() {
            var view = {name: 'foo', content: 'foo', scope: 'global'};

            viewsProtocol.unlink.resolves(view);
            return helper.request().delete('/api/2.0/views/foo')
                .expect(204)
                .expect(function() {
                    expect(viewsProtocol.unlink).to.have.been.calledOnce;
                    expect(viewsProtocol.unlink).to.have.been.calledWith('foo');
                });
        });

        it('should fail to delete non-existant view', function() {
            viewsProtocol.unlink.resolves();
            return helper.request().delete('/api/2.0/views/foo')
                .expect('Content-Type', /^application\/json/)
                .expect(404)
                .expect(function() {
                    expect(viewsProtocol.unlink).to.have.been.calledOnce;
                    expect(viewsProtocol.unlink).to.have.been.calledWith('foo');
                });
        });
    });
});
<commit_msg>fix travisci error by refactor a Http.Api.Views test case<commit_after>// Copyright 2015, EMC, Inc.
/* jshint node:true */

'use strict';

describe('Http.Api.Views', function () {
    var viewsProtocol;
    var _;

    before('start HTTP server', function () {
        this.timeout(10000);
        viewsProtocol = {
            load: sinon.stub(),
            getAll: sinon.stub(),
            get: sinon.stub(),
            put: sinon.stub(),
            unlink: sinon.stub()
        };

        return helper.startServer([
            dihelper.simpleWrapper(viewsProtocol, 'Views')
        ]).then(function() {
            _ = helper.injector.get('_');
        });
    });

    beforeEach('reset stubs', function () {
        _(viewsProtocol).methods().forEach(function (method) {
            if (_(viewsProtocol).has(method)) {
              viewsProtocol[method].reset();
            }
        }).value();
    });

    after('stop HTTP server', function () {
        return helper.stopServer();
    });

    describe('2.0 Views', function() {
        // should create a view (octet-stream)
        it('should get all views', function() {
            var views = [
                {name: 'foo', content: 'foo', scope: 'global'},
                {name: 'bar', content: 'bar', scope: 'global'}
            ];
            viewsProtocol.getAll.resolves(views);
            return helper.request().get('/api/2.0/views')
                .expect('Content-Type', /^application\/json/)
                .expect(200)
                .expect(function(res) {
                    res.body.forEach(function(view, i) {
                        expect(view).to.deep.equal(views[i]);
                    });
                    expect(viewsProtocol.getAll).to.have.been.calledOnce;
                });
        });

        it('should get one view', function() {
            var view = {name: 'foo', content: 'foo', scope: 'global'};

            viewsProtocol.get.resolves(view);
            return helper.request().get('/api/2.0/views/foo')
                .expect('Content-Type', /^application\/json/)
                .expect(200)
                .expect(function(res) {
                    expect(res.body).to.deep.equal(view);
                    expect(viewsProtocol.get).to.have.been.calledOnce;
                    expect(viewsProtocol.get).to.have.been.calledWith('foo');
                });
        });

        it('should fail to get non-existant view', function() {
            viewsProtocol.get.resolves();
            return helper.request().get('/api/2.0/views/foo')
                .expect('Content-Type', /^application\/json/)
                .expect(404)
                .expect(function() {
                    expect(viewsProtocol.get).to.have.been.calledOnce;
                    expect(viewsProtocol.get).to.have.been.calledWith('foo');
                });
        });

        it('should create a text/plain view', function() {
            var view = {name: 'foo', content: 'foo', scope: 'global'};

            viewsProtocol.put.resolves(view);
            return helper.request().put('/api/2.0/views/foo')
                .set('Content-Type', 'text/plain')
                .send('{ "message": "hello" }')
                .expect('Content-Type', /^application\/json/)
                .expect(200)
                .expect(function(res) {
                    expect(res.body).to.deep.equal(view);
                    expect(viewsProtocol.put).to.have.been.calledOnce;
                });
        });

        it('should create an application/octet-stream view', function() {
            var view = {name: 'foo', content: 'foo', scope: 'global'};
            viewsProtocol.put.resolves(view);

            return helper.request().put('/api/2.0/views/foo')
                .set('Content-Type', 'application/octet-stream')
                .send(new Buffer('{ "message": "hello" }', 'ascii'))
                .expect(200)
                .expect(function(res) {
                    expect(res.get('Content-Type')).to.match(/^application\/json/);
                    expect(res.body).to.deep.equal(view);
                    expect(viewsProtocol.put).to.have.been.calledOnce;
                });
        });

        it('should reject invalid data type', function() {
            return helper.request().put('/api/2.0/views/foo')
                .set('Content-Type', 'text/html')
                .send('{ "message": "hello" }')
                .expect('Content-Type', /^application\/json/)
                .expect(400)
                .expect(function() {
                    expect(viewsProtocol.put).not.to.have.been.called;
                });
        });

        it('should delete a view', function() {
            var view = {name: 'foo', content: 'foo', scope: 'global'};

            viewsProtocol.unlink.resolves(view);
            return helper.request().delete('/api/2.0/views/foo')
                .expect(204)
                .expect(function() {
                    expect(viewsProtocol.unlink).to.have.been.calledOnce;
                    expect(viewsProtocol.unlink).to.have.been.calledWith('foo');
                });
        });

        it('should fail to delete non-existant view', function() {
            viewsProtocol.unlink.resolves();
            return helper.request().delete('/api/2.0/views/foo')
                .expect('Content-Type', /^application\/json/)
                .expect(404)
                .expect(function() {
                    expect(viewsProtocol.unlink).to.have.been.calledOnce;
                    expect(viewsProtocol.unlink).to.have.been.calledWith('foo');
                });
        });
    });
});
<|endoftext|>