<commit_before>class MegaMenu {
  constructor(menuElement, openMenuElement, closeMenuElement) {
    this.menu = menuElement;
    this.closeAll = this.closeAll.bind(this);
    this.closeControl = closeMenuElement;
    this.closeMenu = this.closeMenu.bind(this);
    this.addListeners = this.addListeners.bind(this);
    this.getMenu = this.getMenu.bind(this);
    this.hideMenu = this.hideMenu.bind(this);
    this.isWideScreen = this.isWideScreen.bind(this);
    this.openControl = openMenuElement;
    this.openMenu = this.openMenu.bind(this);
    this.resetMenu = this.resetMenu.bind(this);
    this.showMenu = this.showMenu.bind(this);
    this.toggleMenu = this.toggleMenu.bind(this);
    this.toggleSubMenu = this.toggleSubMenu.bind(this);

    this.addListeners();
  }

  addListeners() {
    const menus = Array.from(this.menu.querySelectorAll('.vetnav-level1'));
    const submenus = Array.from(this.menu.querySelectorAll('.vetnav-level2'));
    const backs = Array.from(this.menu.querySelectorAll('.vetnav-back'));

    menus.forEach((menu) => {
      menu.addEventListener('click', this.toggleMenu);
    });

    menus.forEach((menu) => {
      menu.addEventListener('touchend', this.toggleMenu);
    });

    submenus.forEach((submenu) => {
      submenu.addEventListener('click', this.toggleSubMenu);
    });

    backs.forEach((back) => {
      back.addEventListener('click', this.closeMenu);
    });

    this.openControl.addEventListener('click', this.showMenu);
    this.closeControl.addEventListener('click', this.hideMenu);

    window.addEventListener('resize', this.resetMenu);

  }

  closeAll() {
    const menus = this.menu.querySelectorAll('[aria-expanded=true]');
    Array.from(menus).forEach((m) => {
      const whichMenu = this.getMenu(m.getAttribute('aria-controls'));
      whichMenu.setAttribute('hidden','hidden');
      m.setAttribute('aria-expanded', false);
    });
  }

  closeMenu(event) {
    const target = event.target;
    const menu = target.getAttribute('aria-controls');

    target.setAttribute('aria-expanded', false);
    this.getMenu(target.getAttribute('aria-controls')).setAttribute('hidden','hidden');
  }

  getMenu(idName) {
    const selector = `#${idName}`;
    return this.menu.querySelector(selector);
  }

  isWideScreen() {
    return matchMedia('(min-width: 768px)').matches;
  }

  openMenu(event) {
    const target = event.target;
    const menu = target.getAttribute('aria-controls');

    target.setAttribute('aria-expanded', true);
    this.getMenu(menu).removeAttribute('hidden','hidden');
  }

  toggleMenu(event) {
    const eTarget = event.target;
    const whichMenu = this.getMenu(eTarget.getAttribute('aria-controls'));

    if(eTarget.getAttribute('aria-expanded') === 'true') {
      this.closeMenu(event);

    } else {
      this.closeAll();
      this.openMenu(event);

      /*
      Open the first sub-menu and expand first trigger
      when the breakpoint > 768
      */
      if(this.isWideScreen() && whichMenu.querySelector('.vetnav-panel--submenu')){
        whichMenu.querySelector('.vetnav-trigger').setAttribute('aria-expanded', true);
        whichMenu.querySelector('.vetnav-panel--submenu').removeAttribute('hidden');
      }
    }
  }

  toggleSubMenu(event) {
    const submenus = Array.from(this.menu.querySelectorAll('.vetnav-panel--submenu'));
    const triggers = Array.from(this.menu.querySelectorAll('.vetnav-level2'));

    submenus.forEach((sm) => {
      sm.setAttribute('hidden','hidden');
    });

    triggers.forEach((sm) => {
      sm.setAttribute('aria-expanded', false);
    });

    const showCurrent = submenus.find((sm) => {
      return sm.id == event.target.getAttribute('aria-controls');
    });

    showCurrent.removeAttribute('hidden');
    event.target.setAttribute('aria-expanded', true);
  }

  resetMenu() {
    if(this.isWideScreen()) {
      this.closeAll();
      this.showMenu();
    } else {
      this.hideMenu();
    }
    document.body.classList.remove('va-pos-fixed');
  }

  showMenu() {
    document.body.classList.add('va-pos-fixed');
    this.openControl.setAttribute('hidden','hidden');
    this.menu.removeAttribute('hidden');
    this.closeControl.removeAttribute('hidden');
  }

  hideMenu() {
    document.body.classList.remove('va-pos-fixed');
    this.closeControl.setAttribute('hidden','hidden');
    this.menu.setAttribute('hidden','hidden');
    this.openControl.removeAttribute('hidden');
  }
}

export default MegaMenu;

function reInitMenu() {
  const menuElement = document.querySelector('#vetnav');
  const openMenuElement = document.querySelector('.vetnav-controller-open');
  const closeMenuElement = document.querySelector('.vetnav-controller-close');

  const mm = new MegaMenu(menuElement, openMenuElement, closeMenuElement);
  mm.resetMenu();
}

document.addEventListener('DOMContentLoaded', reInitMenu);
<commit_msg>Removing redundant foreach loop<commit_after>class MegaMenu {
  constructor(menuElement, openMenuElement, closeMenuElement) {
    this.menu = menuElement;
    this.closeAll = this.closeAll.bind(this);
    this.closeControl = closeMenuElement;
    this.closeMenu = this.closeMenu.bind(this);
    this.addListeners = this.addListeners.bind(this);
    this.getMenu = this.getMenu.bind(this);
    this.hideMenu = this.hideMenu.bind(this);
    this.isWideScreen = this.isWideScreen.bind(this);
    this.openControl = openMenuElement;
    this.openMenu = this.openMenu.bind(this);
    this.resetMenu = this.resetMenu.bind(this);
    this.showMenu = this.showMenu.bind(this);
    this.toggleMenu = this.toggleMenu.bind(this);
    this.toggleSubMenu = this.toggleSubMenu.bind(this);

    this.addListeners();
  }

  addListeners() {
    const menus = Array.from(this.menu.querySelectorAll('.vetnav-level1'));
    const submenus = Array.from(this.menu.querySelectorAll('.vetnav-level2'));
    const backs = Array.from(this.menu.querySelectorAll('.vetnav-back'));

    menus.forEach((menu) => {
      menu.addEventListener('click', this.toggleMenu);
    });

    submenus.forEach((submenu) => {
      submenu.addEventListener('click', this.toggleSubMenu);
    });

    backs.forEach((back) => {
      back.addEventListener('click', this.closeMenu);
    });

    this.openControl.addEventListener('click', this.showMenu);
    this.closeControl.addEventListener('click', this.hideMenu);

    window.addEventListener('resize', this.resetMenu);

  }

  closeAll() {
    const menus = this.menu.querySelectorAll('[aria-expanded=true]');
    Array.from(menus).forEach((m) => {
      const whichMenu = this.getMenu(m.getAttribute('aria-controls'));
      whichMenu.setAttribute('hidden','hidden');
      m.setAttribute('aria-expanded', false);
    });
  }

  closeMenu(event) {
    const target = event.target;
    const menu = target.getAttribute('aria-controls');

    target.setAttribute('aria-expanded', false);
    this.getMenu(target.getAttribute('aria-controls')).setAttribute('hidden','hidden');
  }

  getMenu(idName) {
    const selector = `#${idName}`;
    return this.menu.querySelector(selector);
  }

  isWideScreen() {
    return matchMedia('(min-width: 768px)').matches;
  }

  openMenu(event) {
    const target = event.target;
    const menu = target.getAttribute('aria-controls');

    target.setAttribute('aria-expanded', true);
    this.getMenu(menu).removeAttribute('hidden','hidden');
  }

  toggleMenu(event) {
    const eTarget = event.target;
    const whichMenu = this.getMenu(eTarget.getAttribute('aria-controls'));

    console.log(eTarget.getAttribute('aria-controls'));

    if(eTarget.getAttribute('aria-expanded') === 'true') {
      this.closeMenu(event);

    } else {
      this.closeAll();
      this.openMenu(event);

      /*
      Open the first sub-menu and expand first trigger
      when the breakpoint > 768
      */
      if(this.isWideScreen() && whichMenu.querySelector('.vetnav-panel--submenu')){
        whichMenu.querySelector('.vetnav-trigger').setAttribute('aria-expanded', true);
        whichMenu.querySelector('.vetnav-panel--submenu').removeAttribute('hidden');
      }
    }
  }

  toggleSubMenu(event) {
    const submenus = Array.from(this.menu.querySelectorAll('.vetnav-panel--submenu'));
    const triggers = Array.from(this.menu.querySelectorAll('.vetnav-level2'));

    submenus.forEach((sm) => {
      sm.setAttribute('hidden','hidden');
    });

    triggers.forEach((sm) => {
      sm.setAttribute('aria-expanded', false);
    });

    const showCurrent = submenus.find((sm) => {
      return sm.id == event.target.getAttribute('aria-controls');
    });

    showCurrent.removeAttribute('hidden');
    event.target.setAttribute('aria-expanded', true);
  }

  resetMenu() {
    if(this.isWideScreen()) {
      this.closeAll();
      this.showMenu();
    } else {
      this.hideMenu();
    }
    document.body.classList.remove('va-pos-fixed');
  }

  showMenu() {
    document.body.classList.add('va-pos-fixed');
    this.openControl.setAttribute('hidden','hidden');
    this.menu.removeAttribute('hidden');
    this.closeControl.removeAttribute('hidden');
  }

  hideMenu() {
    document.body.classList.remove('va-pos-fixed');
    this.closeControl.setAttribute('hidden','hidden');
    this.menu.setAttribute('hidden','hidden');
    this.openControl.removeAttribute('hidden');
  }
}

export default MegaMenu;

function reInitMenu() {
  const menuElement = document.querySelector('#vetnav');
  const openMenuElement = document.querySelector('.vetnav-controller-open');
  const closeMenuElement = document.querySelector('.vetnav-controller-close');

  const mm = new MegaMenu(menuElement, openMenuElement, closeMenuElement);
  mm.resetMenu();
}

document.addEventListener('DOMContentLoaded', reInitMenu);
<|endoftext|>


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

/**
 * @typedef {Object} PlayBillingLookupItem
 * @property {string} itemId - ID of the item
 * @property {PurchaseType} purchaseType - Type of purchase
 */

/**
 * Play Billing Service
 *
 * Creates an instance of Play Billing Service that can be used to make purchases from Play from within a PWA installed from Play using the Digital Goods API
 */
export class PlayBillingService {
  /**
   * @param {PlayBillingLookupItem[]} lookups - Array of lookup items (itemId, purchaseType)
   */
  constructor(lookups) {
    this.lookups = Object.freeze(lookups);
    this.serviceURL = 'https://play.google.com/billing';
    this.init();
  }

  /**
   * Runs async initialization code
   * @private
   */
  async init() {
    // Get service
    if (!this.service) {
      this.service = Object.freeze(await window.getDigitalGoodsService(this.serviceURL));
    }

    // Get item details
    if (!this.skus) {
      await this.updateSkus();
    }
  }

  /**
   * Returns the purchase type for the provided purchase
   * @private
   * @param {PurchaseDetails} purchase - Purchase to check type for
   * @return {PurchaseType}
   */
  _getPurchaseType(purchase) {
    const skuMatch = this.skus.find((sku) => sku.itemId === purchase.itemId);
    const purchaseType = skuMatch ? skuMatch.purchaseType : 'onetime';
    return purchaseType;
  }

  /**
   * Updates service's SKUs with the latest info from Play.
   * Optionally can update the available SKUs for this service, too
   * @param {PlayBillingLookupItem[]} lookups - Array of lookup items (itemId, purchaseType)
   */
  async updateSkus(lookups) {
    if (lookups) {
      this.lookups = Object.freeze(lookups);
    }

    const details = await this.service.getDetails(this.lookups.map((sku) => sku.itemId));
    this.skus = Object.freeze(
      details.map((d) =>
        Object.assign(
          d,
          this.lookups.find((s) => s.itemId === d.itemId),
        ),
      ),
    );
  }

  /**
   * List existing entitlements that haven't been consumed yet or are on-going subscriptions
   * @return {PurchaseDetails[]} Also includes the purchase's purchase type
   */
  async getPurchases() {
    if (!this.skus) {
      await this.init();
    }
    let purchases = (await this.service.listPurchases()).map((p) =>
      Object.assign(p, { purchaseType: this._getPurchaseType(p) }),
    );

    // Acknowledge any un-acknowledged purchases.
    let acknowledged = false;
    for (const purchase of purchases) {
      if (purchase.purchaseState == 'purchased' && !purchase.acknowledged) {
        await this.acknowledge(purchase.purchaseToken, purchase);
        acknowledged = true;
      }
    }

    // Update purchases again after acknowledging un-acknowledged purchases
    if (acknowledged) {
      purchases = (await this.service.listPurchases()).map((p) =>
        Object.assign(p, { purchaseType: this._getPurchaseType(p) }),
      );
    }

    return Object.freeze(purchases);
  }

  /**
   * List available SKUs
   * @return {PlayBillingServiceSku[]}
   */
  async getSkus() {
    if (!this.skus) await this.init();

    return this.skus;
  }

  /**
   * Get the SKU's price in the provided currency
   * @param {PlayBillingServiceSku} sku - SKU to determine price for
   * @param {string} locale - Locale to convert price into
   * @return {string}
   */
  getSkuPrice(sku, locale) {
    return new Intl.NumberFormat(locale, {
      style: 'currency',
      currency: sku.price.currency,
    }).format(Number(sku.price.value));
  }

  /**
   * Apps should acknowledge the purchase after confirming that the purchase token
   * has been associated with a user. This app only acknowledges purchases after
   * successfully receiving the subscription data back from the server.
   *
   * Developers can choose to acknowledge purchases from a server using the
   * Google Play Developer API. The server has direct access to the user database,
   * so using the Google Play Developer API for acknowledgement might be more reliable.
   *
   * If the purchase token is not acknowledged within 3 days,
   * then Google Play will automatically refund and revoke the purchase.
   * This behavior helps ensure that users are not charged for subscriptions unless the
   * user has successfully received access to the content.
   * This eliminates a category of issues where users complain to developers
   * that they paid for something that the app is not giving to them.
   * @param {string} token - Purchase token
   * @param {PurchaseDetailsWithType|PlayBillingServiceSku} item - SKU or purchase to acknowledge
   */
  async acknowledge(token, item) {
    if (!this.service) {
      await this.init();
    }

    // Subscriptions need to be acknowledges as 'onetime'
    const type = item.purchaseType === 'subscription' ? 'onetime' : item.purchaseType;

    return await this.service.acknowledge(token, type);
  }

  /**
   * Forces a purchase token to be consumed.
   * Purchases are coerced into a 'repeatable' type as that's the only type that will trigger consumption in Play
   * @param {string} token - Purchase token
   */
  async consume(token) {
    return await this.acknowledge(token, { purchaseType: 'repeatable' });
  }

  /**
   * Request that the provided SKU is purchased. Will acknowledge purchase on success
   * @param {PlayBillingServiceSku|string} purchaseSku - Either the SKU ID to purchase, or the actual SKU
   * @param {PurchaseDetails} oldPurchase - The previous subscription purchase to be replaced
   * @param {string} subType - The type of subscription purchase (upgrade, downgrade, or normal purchase)
   */
  async purchase(purchaseSku, oldPurchase, subType) {
    if (!this.service || !this.skus) await this.init();

    const sku =
      typeof purchaseSku === 'string'
        ? this.skus.find((s) => s.itemId === purchaseSku)
        : purchaseSku;

    /*
    Set appropriate proration mode based on scenario.
    See https://developer.android.com/google/play/billing/subscriptions#proration-recommendations
    for more information about the different proration modes and recommendations.
    */
    let prorationMode = 'deferred';
    if (subType === 'upgrade') {
      prorationMode = 'immediateAndChargeProratedPrice';
    }
    // Build payment request
    const paymentMethod = [
      {
        supportedMethods: this.serviceURL,
        data: {
          sku: sku.itemId,
          oldSku: oldPurchase?.itemId,
          purchaseToken: oldPurchase?.purchaseToken,
          prorationMode: prorationMode,
        },
      },
    ];

    const payment = new PaymentRequest(paymentMethod);
    const response = await payment.show();
    const { token } = response.details;

    // Call backend to validate the purchase
    const valid = await this.validatePurchaseOnBackend(sku, token);
    if (valid) {
      // Tell PaymentRequest API validation was successful.
      // The user-agent may show a "payment successul" message to the user.
      await response.complete('success');
    } else {
      // Tell PaymentRequest API validation failed.
      // The user-agent may show a message to the user.
      await response.complete('fail');
    }

    return { response, valid };
  }

  /**
   *
   * @param {PlayBillingServiceSku} sku
   * @param {string} token
   */
  async validatePurchaseOnBackend(sku, token) {
    let request = '';

    if (sku.purchaseType === 'subscription') {
      request = '/api/validateSubPurchase';
    } else {
      request = '/api/validatePurchase';
    }

    try {
      const response = await (
        await fetch(request, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            sku: sku.itemId,
            token,
          }),
        })
      ).json();

      if (!('error' in response)) {
        return response.status;
      } else {
        return false;
      }
    } catch (error) {
      throw new Error(`Failed to verify purchase token: ${error.message}`);
    }
  }
}
<commit_msg>Change token to purchaseToken for paymentResponse.details<commit_after>/*
 *  Copyright 2021 Google LLC
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/**
 * @typedef {Object} PlayBillingLookupItem
 * @property {string} itemId - ID of the item
 * @property {PurchaseType} purchaseType - Type of purchase
 */

/**
 * Play Billing Service
 *
 * Creates an instance of Play Billing Service that can be used to make purchases from Play from within a PWA installed from Play using the Digital Goods API
 */
export class PlayBillingService {
  /**
   * @param {PlayBillingLookupItem[]} lookups - Array of lookup items (itemId, purchaseType)
   */
  constructor(lookups) {
    this.lookups = Object.freeze(lookups);
    this.serviceURL = 'https://play.google.com/billing';
    this.init();
  }

  /**
   * Runs async initialization code
   * @private
   */
  async init() {
    // Get service
    if (!this.service) {
      this.service = Object.freeze(await window.getDigitalGoodsService(this.serviceURL));
    }

    // Get item details
    if (!this.skus) {
      await this.updateSkus();
    }
  }

  /**
   * Returns the purchase type for the provided purchase
   * @private
   * @param {PurchaseDetails} purchase - Purchase to check type for
   * @return {PurchaseType}
   */
  _getPurchaseType(purchase) {
    const skuMatch = this.skus.find((sku) => sku.itemId === purchase.itemId);
    const purchaseType = skuMatch ? skuMatch.purchaseType : 'onetime';
    return purchaseType;
  }

  /**
   * Updates service's SKUs with the latest info from Play.
   * Optionally can update the available SKUs for this service, too
   * @param {PlayBillingLookupItem[]} lookups - Array of lookup items (itemId, purchaseType)
   */
  async updateSkus(lookups) {
    if (lookups) {
      this.lookups = Object.freeze(lookups);
    }

    const details = await this.service.getDetails(this.lookups.map((sku) => sku.itemId));
    this.skus = Object.freeze(
      details.map((d) =>
        Object.assign(
          d,
          this.lookups.find((s) => s.itemId === d.itemId),
        ),
      ),
    );
  }

  /**
   * List existing entitlements that haven't been consumed yet or are on-going subscriptions
   * @return {PurchaseDetails[]} Also includes the purchase's purchase type
   */
  async getPurchases() {
    if (!this.skus) {
      await this.init();
    }
    let purchases = (await this.service.listPurchases()).map((p) =>
      Object.assign(p, { purchaseType: this._getPurchaseType(p) }),
    );

    // Acknowledge any un-acknowledged purchases.
    let acknowledged = false;
    for (const purchase of purchases) {
      if (purchase.purchaseState == 'purchased' && !purchase.acknowledged) {
        await this.acknowledge(purchase.purchaseToken, purchase);
        acknowledged = true;
      }
    }

    // Update purchases again after acknowledging un-acknowledged purchases
    if (acknowledged) {
      purchases = (await this.service.listPurchases()).map((p) =>
        Object.assign(p, { purchaseType: this._getPurchaseType(p) }),
      );
    }

    return Object.freeze(purchases);
  }

  /**
   * List available SKUs
   * @return {PlayBillingServiceSku[]}
   */
  async getSkus() {
    if (!this.skus) await this.init();

    return this.skus;
  }

  /**
   * Get the SKU's price in the provided currency
   * @param {PlayBillingServiceSku} sku - SKU to determine price for
   * @param {string} locale - Locale to convert price into
   * @return {string}
   */
  getSkuPrice(sku, locale) {
    return new Intl.NumberFormat(locale, {
      style: 'currency',
      currency: sku.price.currency,
    }).format(Number(sku.price.value));
  }

  /**
   * Apps should acknowledge the purchase after confirming that the purchase token
   * has been associated with a user. This app only acknowledges purchases after
   * successfully receiving the subscription data back from the server.
   *
   * Developers can choose to acknowledge purchases from a server using the
   * Google Play Developer API. The server has direct access to the user database,
   * so using the Google Play Developer API for acknowledgement might be more reliable.
   *
   * If the purchase token is not acknowledged within 3 days,
   * then Google Play will automatically refund and revoke the purchase.
   * This behavior helps ensure that users are not charged for subscriptions unless the
   * user has successfully received access to the content.
   * This eliminates a category of issues where users complain to developers
   * that they paid for something that the app is not giving to them.
   * @param {string} token - Purchase token
   * @param {PurchaseDetailsWithType|PlayBillingServiceSku} item - SKU or purchase to acknowledge
   */
  async acknowledge(token, item) {
    if (!this.service) {
      await this.init();
    }

    // Subscriptions need to be acknowledges as 'onetime'
    const type = item.purchaseType === 'subscription' ? 'onetime' : item.purchaseType;

    return await this.service.acknowledge(token, type);
  }

  /**
   * Forces a purchase token to be consumed.
   * Purchases are coerced into a 'repeatable' type as that's the only type that will trigger consumption in Play
   * @param {string} token - Purchase token
   */
  async consume(token) {
    return await this.acknowledge(token, { purchaseType: 'repeatable' });
  }

  /**
   * Request that the provided SKU is purchased. Will acknowledge purchase on success
   * @param {PlayBillingServiceSku|string} purchaseSku - Either the SKU ID to purchase, or the actual SKU
   * @param {PurchaseDetails} oldPurchase - The previous subscription purchase to be replaced
   * @param {string} subType - The type of subscription purchase (upgrade, downgrade, or normal purchase)
   */
  async purchase(purchaseSku, oldPurchase, subType) {
    if (!this.service || !this.skus) await this.init();

    const sku =
      typeof purchaseSku === 'string'
        ? this.skus.find((s) => s.itemId === purchaseSku)
        : purchaseSku;

    /*
    Set appropriate proration mode based on scenario.
    See https://developer.android.com/google/play/billing/subscriptions#proration-recommendations
    for more information about the different proration modes and recommendations.
    */
    let prorationMode = 'deferred';
    if (subType === 'upgrade') {
      prorationMode = 'immediateAndChargeProratedPrice';
    }
    // Build payment request
    const paymentMethod = [
      {
        supportedMethods: this.serviceURL,
        data: {
          sku: sku.itemId,
          oldSku: oldPurchase?.itemId,
          purchaseToken: oldPurchase?.purchaseToken,
          prorationMode: prorationMode,
        },
      },
    ];

    const payment = new PaymentRequest(paymentMethod);
    const response = await payment.show();
    const { purchaseToken } = response.details;

    // Call backend to validate the purchase
    const valid = await this.validatePurchaseOnBackend(sku, purchaseToken);
    if (valid) {
      // Tell PaymentRequest API validation was successful.
      // The user-agent may show a "payment successul" message to the user.
      await response.complete('success');
    } else {
      // Tell PaymentRequest API validation failed.
      // The user-agent may show a message to the user.
      await response.complete('fail');
    }

    return { response, valid };
  }

  /**
   *
   * @param {PlayBillingServiceSku} sku
   * @param {string} token
   */
  async validatePurchaseOnBackend(sku, token) {
    let request = '';

    if (sku.purchaseType === 'subscription') {
      request = '/api/validateSubPurchase';
    } else {
      request = '/api/validatePurchase';
    }

    try {
      const response = await (
        await fetch(request, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            sku: sku.itemId,
            token,
          }),
        })
      ).json();

      if (!('error' in response)) {
        return response.status;
      } else {
        return false;
      }
    } catch (error) {
      throw new Error(`Failed to verify purchase token: ${error.message}`);
    }
  }
}
<|endoftext|>


<commit_before>'use strict';

angular.module('CollaborativeMap')
.directive('history', ['$http', function($http){

	return {
		// name: '',
		// priority: 1,
		// terminal: true,
		// scope: {}, // {} = isolate, true = child, false/undefined = no change
		// controller: function($scope, $element, $attrs, $transclude) {},
		// require: 'ngModel', // Array = multiple requires, ? = optional, ^ = check parent elements
		restrict: 'E', // E = Element, A = Attribute, C = Class, M = Comment
		// template: '',
		templateUrl: 'partials/history',
		replace: true,
		// replace: true,
		// transclude: true,
		// compile: function(tElement, tAttrs, function transclude(function(scope, cloneLinkingFn){ return function linking(scope, elm, attrs){}})),
		link: function($scope, iElm, iAttrs, controller) {
			var visible = false;

			function loadHistory(fid){
				console.log('load history', fid);
			}

			$scope.toggleHistoryModal= function(fid){
				visible = !visible;
				$('#historyModal').modal('toggle');
				if(visible){
					loadHistory(fid);
				}
			};
		}
	};
}]);<commit_msg>minor changes<commit_after>'use strict';

angular.module('CollaborativeMap')
.directive('history', ['$http', function($http){

	return {
		restrict: 'E', // E = Element, A = Attribute, C = Class, M = Comment
		templateUrl: 'partials/history',
		replace: true,
		// transclude: true,
		// compile: function(tElement, tAttrs, function transclude(function(scope, cloneLinkingFn){ return function linking(scope, elm, attrs){}})),
		link: function($scope, iElm, iAttrs, controller) {
			var visible = false;

			function loadHistory(fid){
				console.log('load history', fid);
			}

			$scope.toggleHistoryModal= function(fid){
				visible = !visible;
				$('#historyModal').modal('toggle');
				if(visible){
					loadHistory(fid);
				}
			};
		}
	};
}]);<|endoftext|>


<commit_before>define([
  'jquery',
  'require',

  './results',

  './selection/single',
  './selection/multiple',
  './selection/placeholder',
  './selection/allowClear',
  './selection/search',
  './selection/eventRelay',

  './utils',
  './translation',
  './diacritics',

  './data/select',
  './data/array',
  './data/ajax',
  './data/tags',
  './data/tokenizer',
  './data/minimumInputLength',
  './data/maximumInputLength',
  './data/maximumSelectionLength',

  './dropdown',
  './dropdown/search',
  './dropdown/hidePlaceholder',
  './dropdown/infiniteScroll',
  './dropdown/attachBody',
  './dropdown/minimumResultsForSearch',
  './dropdown/selectOnClose',
  './dropdown/closeOnSelect',

  './i18n/en'
], function ($, require,

             ResultsList,

             SingleSelection, MultipleSelection, Placeholder, AllowClear,
             SelectionSearch, EventRelay,

             Utils, Translation, DIACRITICS,

             SelectData, ArrayData, AjaxData, Tags, Tokenizer,
             MinimumInputLength, MaximumInputLength, MaximumSelectionLength,

             Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll,
             AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect,

             EnglishTranslation) {
  function Defaults () {
    this.reset();
  }

  Defaults.prototype.apply = function (options) {
    options = $.extend(true, {}, this.defaults, options);

    if (options.dataAdapter == null) {
      if (options.ajax != null) {
        options.dataAdapter = AjaxData;
      } else if (options.data != null) {
        options.dataAdapter = ArrayData;
      } else {
        options.dataAdapter = SelectData;
      }

      if (options.minimumInputLength > 0) {
        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          MinimumInputLength
        );
      }

      if (options.maximumInputLength > 0) {
        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          MaximumInputLength
        );
      }

      if (options.maximumSelectionLength > 0) {
        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          MaximumSelectionLength
        );
      }

      if (options.tags) {
        options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags);
      }

      if (options.tokenSeparators != null || options.tokenizer != null) {
        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          Tokenizer
        );
      }

      if (options.query != null) {
        var Query = require(options.amdBase + 'compat/query');

        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          Query
        );
      }

      if (options.initSelection != null) {
        var InitSelection = require(options.amdBase + 'compat/initSelection');

        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          InitSelection
        );
      }
    }

    if (options.resultsAdapter == null) {
      options.resultsAdapter = ResultsList;

      if (options.ajax != null) {
        options.resultsAdapter = Utils.Decorate(
          options.resultsAdapter,
          InfiniteScroll
        );
      }

      if (options.placeholder != null) {
        options.resultsAdapter = Utils.Decorate(
          options.resultsAdapter,
          HidePlaceholder
        );
      }

      if (options.selectOnClose) {
        options.resultsAdapter = Utils.Decorate(
          options.resultsAdapter,
          SelectOnClose
        );
      }
    }

    if (options.dropdownAdapter == null) {
      if (options.multiple) {
        options.dropdownAdapter = Dropdown;
      } else {
        var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch);

        options.dropdownAdapter = SearchableDropdown;
      }

      if (options.minimumResultsForSearch !== 0) {
        options.dropdownAdapter = Utils.Decorate(
          options.dropdownAdapter,
          MinimumResultsForSearch
        );
      }

      if (options.closeOnSelect) {
        options.dropdownAdapter = Utils.Decorate(
          options.dropdownAdapter,
          CloseOnSelect
        );
      }

      if (
        options.dropdownCssClass != null ||
        options.dropdownCss != null ||
        options.adaptDropdownCssClass != null
      ) {
        var DropdownCSS = require(options.amdBase + 'compat/dropdownCss');

        options.dropdownAdapter = Utils.Decorate(
          options.dropdownAdapter,
          DropdownCSS
        );
      }

      options.dropdownAdapter = Utils.Decorate(
        options.dropdownAdapter,
        AttachBody
      );
    }

    if (options.selectionAdapter == null) {
      if (options.multiple) {
        options.selectionAdapter = MultipleSelection;
      } else {
        options.selectionAdapter = SingleSelection;
      }

      // Add the placeholder mixin if a placeholder was specified
      if (options.placeholder != null) {
        options.selectionAdapter = Utils.Decorate(
          options.selectionAdapter,
          Placeholder
        );
      }

      if (options.allowClear) {
        options.selectionAdapter = Utils.Decorate(
          options.selectionAdapter,
          AllowClear
        );
      }

      if (options.multiple) {
        options.selectionAdapter = Utils.Decorate(
          options.selectionAdapter,
          SelectionSearch
        );
      }

      if (
        options.containerCssClass != null ||
        options.containerCss != null ||
        options.adaptContainerCssClass != null
      ) {
        var ContainerCSS = require(options.amdBase + 'compat/containerCss');

        options.selectionAdapter = Utils.Decorate(
          options.selectionAdapter,
          ContainerCSS
        );
      }

      options.selectionAdapter = Utils.Decorate(
        options.selectionAdapter,
        EventRelay
      );
    }

    if (typeof options.language === 'string') {
      // Check if the language is specified with a region
      if (options.language.indexOf('-') > 0) {
        // Extract the region information if it is included
        var languageParts = options.language.split('-');
        var baseLanguage = languageParts[0];

        options.language = [options.language, baseLanguage];
      } else {
        options.language = [options.language];
      }
    }

    if ($.isArray(options.language)) {
      var languages = new Translation();
      options.language.push('en');

      var languageNames = options.language;

      for (var l = 0; l < languageNames.length; l++) {
        var name = languageNames[l];
        var language = {};

        try {
          // Try to load it with the original name
          language = Translation.loadPath(name);
        } catch (e) {
          try {
            // If we couldn't load it, check if it wasn't the full path
            name = this.defaults.amdLanguageBase + name;
            language = Translation.loadPath(name);
          } catch (ex) {
            // The translation could not be loaded at all. Sometimes this is
            // because of a configuration problem, other times this can be
            // because of how Select2 helps load all possible translation files.
            if (options.debug && window.console && console.warn) {
              console.warn(
                'Select2: The language file for "' + name + '" could not be ' +
                'automatically loaded. A fallback will be used instead.'
              );
            }

            continue;
          }
        }

        languages.extend(language);
      }

      options.translations = languages;
    } else {
      var baseTranslation = Translation.loadPath(
        this.defaults.amdLanguageBase + 'en'
      );
      var customTranslation = new Translation(options.language);

      customTranslation.extend(baseTranslation);

      options.translations = customTranslation;
    }

    return options;
  };

  Defaults.prototype.reset = function () {
    function stripDiacritics (text) {
      // Used 'uni range + named function' from http://jsperf.com/diacritics/18
      function match(a) {
        return DIACRITICS[a] || a;
      }

      return text.replace(/[^\u0000-\u007E]/g, match);
    }

    function matcher (params, data) {
      // Always return the object if there is nothing to compare
      if ($.trim(params.term) === '') {
        return data;
      }

      // Do a recursive check for options with children
      if (data.children && data.children.length > 0) {
        // Clone the data object if there are children
        // This is required as we modify the object to remove any non-matches
        var match = $.extend(true, {}, data);

        // Check each child of the option
        for (var c = data.children.length - 1; c >= 0; c--) {
          var child = data.children[c];

          var matches = matcher(params, child);

          // If there wasn't a match, remove the object in the array
          if (matches == null) {
            match.children.splice(c, 1);
          }
        }

        // If any children matched, return the new object
        if (match.children.length > 0) {
          return match;
        }

        // If there were no matching children, check just the plain object
        return matcher(params, match);
      }

      var original = stripDiacritics(data.text).toUpperCase();
      var term = stripDiacritics(params.term).toUpperCase();

      // Check if the text contains the term
      if (original.indexOf(term) > -1) {
        return data;
      }

      // If it doesn't contain the term, don't return anything
      return null;
    }

    this.defaults = {
      amdBase: './',
      amdLanguageBase: './i18n/',
      closeOnSelect: true,
      debug: false,
      dropdownAutoWidth: false,
      escapeMarkup: Utils.escapeMarkup,
      language: EnglishTranslation,
      matcher: matcher,
      minimumInputLength: 0,
      maximumInputLength: 0,
      maximumSelectionLength: 0,
      minimumResultsForSearch: 0,
      selectOnClose: false,
      scrollAfterSelect: true,
      sorter: function (data) {
        return data;
      },
      templateResult: function (result) {
        return result.text;
      },
      templateSelection: function (selection) {
        return selection.text;
      },
      theme: 'default',
      width: 'resolve'
    };
  };

  Defaults.prototype.set = function (key, value) {
    var camelKey = $.camelCase(key);

    var data = {};
    data[camelKey] = value;

    var convertedData = Utils._convertData(data);

    $.extend(true, this.defaults, convertedData);
  };

  var defaults = new Defaults();

  return defaults;
});
<commit_msg>Update defaults.js<commit_after>define([
  'jquery',
  'require',

  './results',

  './selection/single',
  './selection/multiple',
  './selection/placeholder',
  './selection/allowClear',
  './selection/search',
  './selection/eventRelay',

  './utils',
  './translation',
  './diacritics',

  './data/select',
  './data/array',
  './data/ajax',
  './data/tags',
  './data/tokenizer',
  './data/minimumInputLength',
  './data/maximumInputLength',
  './data/maximumSelectionLength',

  './dropdown',
  './dropdown/search',
  './dropdown/hidePlaceholder',
  './dropdown/infiniteScroll',
  './dropdown/attachBody',
  './dropdown/minimumResultsForSearch',
  './dropdown/selectOnClose',
  './dropdown/closeOnSelect',

  './i18n/en'
], function ($, require,

             ResultsList,

             SingleSelection, MultipleSelection, Placeholder, AllowClear,
             SelectionSearch, EventRelay,

             Utils, Translation, DIACRITICS,

             SelectData, ArrayData, AjaxData, Tags, Tokenizer,
             MinimumInputLength, MaximumInputLength, MaximumSelectionLength,

             Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll,
             AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect,

             EnglishTranslation) {
  function Defaults () {
    this.reset();
  }

  Defaults.prototype.apply = function (options) {
    options = $.extend(true, {}, this.defaults, options);

    if (options.dataAdapter == null) {
      if (options.ajax != null) {
        options.dataAdapter = AjaxData;
      } else if (options.data != null) {
        options.dataAdapter = ArrayData;
      } else {
        options.dataAdapter = SelectData;
      }

      if (options.minimumInputLength > 0) {
        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          MinimumInputLength
        );
      }

      if (options.maximumInputLength > 0) {
        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          MaximumInputLength
        );
      }

      if (options.maximumSelectionLength > 0) {
        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          MaximumSelectionLength
        );
      }

      if (options.tags) {
        options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags);
      }

      if (options.tokenSeparators != null || options.tokenizer != null) {
        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          Tokenizer
        );
      }

      if (options.query != null) {
        var Query = require(options.amdBase + 'compat/query');

        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          Query
        );
      }

      if (options.initSelection != null) {
        var InitSelection = require(options.amdBase + 'compat/initSelection');

        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          InitSelection
        );
      }
    }

    if (options.resultsAdapter == null) {
      options.resultsAdapter = ResultsList;

      if (options.ajax != null) {
        options.resultsAdapter = Utils.Decorate(
          options.resultsAdapter,
          InfiniteScroll
        );
      }

      if (options.placeholder != null) {
        options.resultsAdapter = Utils.Decorate(
          options.resultsAdapter,
          HidePlaceholder
        );
      }

      if (options.selectOnClose) {
        options.resultsAdapter = Utils.Decorate(
          options.resultsAdapter,
          SelectOnClose
        );
      }
    }

    if (options.dropdownAdapter == null) {
      if (options.multiple) {
        options.dropdownAdapter = Dropdown;
      } else {
        var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch);

        options.dropdownAdapter = SearchableDropdown;
      }

      if (options.minimumResultsForSearch !== 0) {
        options.dropdownAdapter = Utils.Decorate(
          options.dropdownAdapter,
          MinimumResultsForSearch
        );
      }

      if (options.closeOnSelect) {
        options.dropdownAdapter = Utils.Decorate(
          options.dropdownAdapter,
          CloseOnSelect
        );
      }

      if (
        options.dropdownCssClass != null ||
        options.dropdownCss != null ||
        options.adaptDropdownCssClass != null
      ) {
        var DropdownCSS = require(options.amdBase + 'compat/dropdownCss');

        options.dropdownAdapter = Utils.Decorate(
          options.dropdownAdapter,
          DropdownCSS
        );
      }

      options.dropdownAdapter = Utils.Decorate(
        options.dropdownAdapter,
        AttachBody
      );
    }

    if (options.selectionAdapter == null) {
      if (options.multiple) {
        options.selectionAdapter = MultipleSelection;
      } else {
        options.selectionAdapter = SingleSelection;
      }

      // Add the placeholder mixin if a placeholder was specified
      if (options.placeholder != null) {
        options.selectionAdapter = Utils.Decorate(
          options.selectionAdapter,
          Placeholder
        );
      }

      if (options.allowClear) {
        options.selectionAdapter = Utils.Decorate(
          options.selectionAdapter,
          AllowClear
        );
      }

      if (options.multiple) {
        options.selectionAdapter = Utils.Decorate(
          options.selectionAdapter,
          SelectionSearch
        );
      }

      if (
        options.containerCssClass != null ||
        options.containerCss != null ||
        options.adaptContainerCssClass != null
      ) {
        var ContainerCSS = require(options.amdBase + 'compat/containerCss');

        options.selectionAdapter = Utils.Decorate(
          options.selectionAdapter,
          ContainerCSS
        );
      }

      options.selectionAdapter = Utils.Decorate(
        options.selectionAdapter,
        EventRelay
      );
    }

    if (typeof options.language === 'string') {
      // Check if the language is specified with a region
      if (options.language.indexOf('-') > 0) {
        // Extract the region information if it is included
        var languageParts = options.language.split('-');
        var baseLanguage = languageParts[0];

        options.language = [options.language, baseLanguage];
      } else {
        options.language = [options.language];
      }
    }

    if ($.isArray(options.language)) {
      var languages = new Translation();
      options.language.push('en');

      var languageNames = options.language;

      for (var l = 0; l < languageNames.length; l++) {
        var name = languageNames[l];
        var language = {};

        try {
          // Try to load it with the original name
          language = Translation.loadPath(name);
        } catch (e) {
          try {
            // If we couldn't load it, check if it wasn't the full path
            name = this.defaults.amdLanguageBase + name;
            language = Translation.loadPath(name);
          } catch (ex) {
            // The translation could not be loaded at all. Sometimes this is
            // because of a configuration problem, other times this can be
            // because of how Select2 helps load all possible translation files.
            if (options.debug && window.console && console.warn) {
              console.warn(
                'Select2: The language file for "' + name + '" could not be ' +
                'automatically loaded. A fallback will be used instead.'
              );
            }

            continue;
          }
        }

        languages.extend(language);
      }

      options.translations = languages;
    } else {
      var baseTranslation = Translation.loadPath(
        this.defaults.amdLanguageBase + 'en'
      );
      var customTranslation = new Translation(options.language);

      customTranslation.extend(baseTranslation);

      options.translations = customTranslation;
    }

    return options;
  };

  Defaults.prototype.reset = function () {
    function stripDiacritics (text) {
      // Used 'uni range + named function' from http://jsperf.com/diacritics/18
      function match(a) {
        return DIACRITICS[a] || a;
      }

      return text.replace(/[^\u0000-\u007E]/g, match);
    }

    function matcher (params, data) {
      // Always return the object if there is nothing to compare
      if ($.trim(params.term) === '') {
        return data;
      }

      // Do a recursive check for options with children
      if (data.children && data.children.length > 0) {
        // Clone the data object if there are children
        // This is required as we modify the object to remove any non-matches
        var match = $.extend(true, {}, data);

        // Check each child of the option
        for (var c = data.children.length - 1; c >= 0; c--) {
          var child = data.children[c];

          var matches = matcher(params, child);

          // If there wasn't a match, remove the object in the array
          if (matches == null) {
            match.children.splice(c, 1);
          }
        }

        // If any children matched, return the new object
        if (match.children.length > 0) {
          return match;
        }

        // If there were no matching children, check just the plain object
        return matcher(params, match);
      }

      var original = stripDiacritics(data.text).toUpperCase();
      var term = stripDiacritics(params.term).toUpperCase();

      // Check if the text contains the term
      if (original.indexOf(term) > -1) {
        return data;
      }

      // If it doesn't contain the term, don't return anything
      return null;
    }

    this.defaults = {
      amdBase: './',
      amdLanguageBase: './i18n/',
      closeOnSelect: true,
      debug: false,
      dropdownAutoWidth: false,
      escapeMarkup: Utils.escapeMarkup,
      language: EnglishTranslation,
      matcher: matcher,
      minimumInputLength: 0,
      maximumInputLength: 0,
      maximumSelectionLength: 0,
      minimumResultsForSearch: 0,
      selectOnClose: false,
      scrollAfterSelect: false,
      sorter: function (data) {
        return data;
      },
      templateResult: function (result) {
        return result.text;
      },
      templateSelection: function (selection) {
        return selection.text;
      },
      theme: 'default',
      width: 'resolve'
    };
  };

  Defaults.prototype.set = function (key, value) {
    var camelKey = $.camelCase(key);

    var data = {};
    data[camelKey] = value;

    var convertedData = Utils._convertData(data);

    $.extend(true, this.defaults, convertedData);
  };

  var defaults = new Defaults();

  return defaults;
});
<|endoftext|>


<commit_before>(function(){

    xtag.register('widget-MSCbuilder', {
        extends: 'div',
        lifecycle: {
            created: function() {
                var title = document.createElement('h1'),
                    optionWrap, message, submit;

                title.innerHTML = 'Detector Deployment'
                this.appendChild(title);

                this.detectorForm = document.createElement('form');
                this.detectorForm.setAttribute('name', 'detectors');
                this.detectorForm.setAttribute('method', 'POST');
                this.detectorForm.setAttribute('action', 'buildMSC');
                this.appendChild(this.detectorForm);

                this.upstreamChamber = document.createElement('div');
                this.upstreamChamber.setAttribute('class', 'detectorSection');
                this.detectorForm.appendChild(this.upstreamChamber);
                title = document.createElement('h2');
                title.innerHTML = 'Upstream Chamber';
                this.upstreamChamber.appendChild(title);
                optionWrap = document.createElement('div');
                optionWrap.setAttribute('class', 'optionWrap');
                this.upstreamChamber.appendChild(optionWrap);
                radioArray(optionWrap, ['Absent', 'SCEPTAR', 'PACES', 'SPICE'], ['USCabsent', 'USCSE', 'USCPA', 'USCSP'], 'USC');

                this.downstreamChamber = document.createElement('div');
                this.downstreamChamber.setAttribute('class', 'detectorSection');
                this.detectorForm.appendChild(this.downstreamChamber);
                title = document.createElement('h2');
                title.innerHTML = 'Downstream Chamber';
                this.downstreamChamber.appendChild(title);
                optionWrap = document.createElement('div');
                optionWrap.setAttribute('class', 'optionWrap');
                this.downstreamChamber.appendChild(optionWrap);
                radioArray(optionWrap, ['Absent', 'SCEPTAR', 'ZDS', 'S2', 'S3'], ['DSCabsent', 'DSCSE', 'DSCZD', 'DSCS2', 'DSCS3'], 'DSC');
                document.getElementById('DSC3Label').setAttribute('style', 'display:none');
                document.getElementById('DSC4Label').setAttribute('style', 'display:none');

                this.corona = document.createElement('div');
                this.corona.setAttribute('class', 'detectorSection');
                this.detectorForm.appendChild(this.corona);
                title = document.createElement('h2');
                title.innerHTML = 'Corona';
                this.corona.appendChild(title);
                optionWrap = document.createElement('div');
                optionWrap.setAttribute('class', 'optionWrap');
                this.corona.appendChild(optionWrap);
                radioArray(optionWrap, ['Absent', 'GRIFFIN'], ['coronaAbsent', 'CoronaGR'], 'corona');
                this.corona.appendChild(this.generateGRIFFINtable('corona', [5,6,7,8,9,10,11,12]));

                this.upstreamLampshade = document.createElement('div');
                this.upstreamLampshade.setAttribute('class', 'detectorSection');
                this.detectorForm.appendChild(this.upstreamLampshade);
                title = document.createElement('h2');
                title.innerHTML = 'Upstream Lampshade';
                this.upstreamLampshade.appendChild(title);
                optionWrap = document.createElement('div');
                optionWrap.setAttribute('class', 'optionWrap');
                this.upstreamLampshade.appendChild(optionWrap);
                radioArray(optionWrap, ['Absent', 'GRIFFIN', 'GRIFFIN + DANTE'], ['USLabsent', 'USLGR', 'USLGRDA'], 'USL');
                this.upstreamLampshade.appendChild(this.generateGRIFFINtable('USL', [13,14,15,16]));
                message = document.createElement('p');
                message.setAttribute('id', 'USLmessage');
                message.setAttribute('class', 'message hidden');
                this.upstreamLampshade.appendChild(message);

                this.downstreamLampshade = document.createElement('div');
                this.downstreamLampshade.setAttribute('class', 'detectorSection');
                this.detectorForm.appendChild(this.downstreamLampshade);
                title = document.createElement('h2');
                title.innerHTML = 'Downstream Lampshade';
                this.downstreamLampshade.appendChild(title);
                optionWrap = document.createElement('div');
                optionWrap.setAttribute('class', 'optionWrap');
                this.downstreamLampshade.appendChild(optionWrap);
                radioArray(optionWrap, ['Absent', 'GRIFFIN', 'GRIFFIN + DANTE', 'DESCANT'], ['DSLabsent', 'DSLGR', 'DSLGRDA', 'DSLDS'], 'DSL')
                this.downstreamLampshade.appendChild(this.generateGRIFFINtable('DSL', [1,2,3,4]));

                submit = document.createElement('button');
                submit.setAttribute('class', 'stdin');
                submit.innerHTML = 'Build DAQ Address Table';
                this.detectorForm.appendChild(submit);

                //state management
                //turn appropriate GRIFFIN tables on as needed, and manage SPICE / GRIFFIN constraint in USL:
                document.getElementById('USC0Label').onclick = this.turnOffSPICE;
                document.getElementById('USC1Label').onclick = this.turnOffSPICE;
                document.getElementById('USC2Label').onclick = this.turnOffSPICE;
                document.getElementById('USC3Label').onclick = function(){
                    var message = document.getElementById('USLmessage')

                    message.setAttribute('class', 'message');
                    message.innerHTML = 'SPICE services occupy the upstream lampshade when SPICE is installed; remove SPICE from the upstream chamber to use GRIFFIN here.'

                    document.forms.detectors.USL[0].checked=true;
                    document.getElementById('USL0Label').onclick();

                    document.getElementById('DSC1Label').setAttribute('style', 'display:none');
                    document.getElementById('DSC2Label').setAttribute('style', 'display:none');
                    document.getElementById('DSC3Label').setAttribute('style', 'display:auto');
                    document.getElementById('DSC4Label').setAttribute('style', 'display:auto');
                    document.forms.detectors.DSC[0].checked=true;

                }

                document.getElementById('corona0Label').onclick = function(){
                    document.getElementById('coronaGRIFFINconfig').setAttribute('class', 'GRIFFINconfig hidden');
                }
                document.getElementById('corona1Label').onclick = function(){
                    document.getElementById('coronaGRIFFINconfig').setAttribute('class', 'GRIFFINconfig');
                }

                document.getElementById('USL0Label').onclick = function(){
                    document.getElementById('USLGRIFFINconfig').setAttribute('class', 'GRIFFINconfig hidden');
                }
                document.getElementById('USL1Label').onclick = function(){
                    var USC = document.querySelectorAll('input[name="USC"]:checked')[0]

                    if(USC && USC.value == 'USCSP')
                        return false;

                    document.getElementById('USLGRIFFINconfig').setAttribute('class', 'GRIFFINconfig');
                }

                document.getElementById('DSL0Label').onclick = function(){
                    document.getElementById('DSLGRIFFINconfig').setAttribute('class', 'GRIFFINconfig hidden');
                }
                document.getElementById('DSL1Label').onclick = function(){
                    document.getElementById('DSLGRIFFINconfig').setAttribute('class', 'GRIFFINconfig');
                }
                document.getElementById('DSL2Label').onclick = function(){
                    document.getElementById('DSLGRIFFINconfig').setAttribute('class', 'GRIFFINconfig hidden');
                }

                //defaults
                document.forms.detectors.USC[0].checked=true;
                document.forms.detectors.DSC[0].checked=true;
                document.forms.detectors.corona[1].checked=true;
                document.forms.detectors.USL[1].checked=true;
                document.forms.detectors.DSL[1].checked=true;
            },
            inserted: function() {},
            removed: function() {},
            attributeChanged: function() {}
        },
        events: {

        },
        accessors: {

        },
        methods: {
            'generateGRIFFINtable' : function(section, clovers){
                var table = document.createElement('table'),
                    row, cell, checkbox,
                    i;

                table.setAttribute('class', 'GRIFFINconfig')
                table.setAttribute('id', section+'GRIFFINconfig')

                row = document.createElement('tr');
                table.appendChild(row);
                cell = document.createElement('td');
                cell.innerHTML = 'Clover';
                row.appendChild(cell);
                for(i=0; i<clovers.length; i++){
                    cell = document.createElement('td');
                    cell.innerHTML = clovers[i];
                    row.appendChild(cell);
                }

                row = document.createElement('tr');
                table.appendChild(row);
                cell = document.createElement('td');
                cell.innerHTML = 'Crystal'
                row.appendChild(cell);
                for(i=0; i<clovers.length; i++){
                    cell = document.createElement('td');
                    checkbox = document.createElement('input');
                    checkbox.setAttribute('type', 'checkbox');
                    checkbox.setAttribute('name', 'crystal' + clovers[i]);
                    checkbox.setAttribute('checked', true);
                    cell.appendChild(checkbox);
                    row.appendChild(cell);
                }

                row = document.createElement('tr');
                table.appendChild(row);
                cell = document.createElement('td');
                cell.innerHTML = 'Suppressor'
                row.appendChild(cell);
                for(i=0; i<clovers.length; i++){
                    cell = document.createElement('td');
                    checkbox = document.createElement('input');
                    checkbox.setAttribute('type', 'checkbox');
                    checkbox.setAttribute('name', 'suppressor' + clovers[i]);
                    //checkbox.setAttribute('checked', true);
                    cell.appendChild(checkbox);
                    row.appendChild(cell);
                }

                return table;
            },

            'turnOffSPICE' : function(){
                var message = document.getElementById('USLmessage')
                message.setAttribute('class', 'message hidden');
                document.getElementById('DSC1Label').setAttribute('style', 'display:auto');
                document.getElementById('DSC2Label').setAttribute('style', 'display:auto');
                document.getElementById('DSC3Label').setAttribute('style', 'display:none');
                document.getElementById('DSC4Label').setAttribute('style', 'display:none');
                document.forms.detectors.DSC[0].checked=true;
            }

        }
    });

})();<commit_msg>working on DANTE deployment for #24<commit_after>(function(){

    xtag.register('widget-MSCbuilder', {
        extends: 'div',
        lifecycle: {
            created: function() {
                var title = document.createElement('h1'),
                    optionWrap, message, submit;

                title.innerHTML = 'Detector Deployment'
                this.appendChild(title);

                this.detectorForm = document.createElement('form');
                this.detectorForm.setAttribute('name', 'detectors');
                this.detectorForm.setAttribute('method', 'POST');
                this.detectorForm.setAttribute('action', 'buildMSC');
                this.appendChild(this.detectorForm);

                this.upstreamChamber = document.createElement('div');
                this.upstreamChamber.setAttribute('class', 'detectorSection');
                this.detectorForm.appendChild(this.upstreamChamber);
                title = document.createElement('h2');
                title.innerHTML = 'Upstream Chamber';
                this.upstreamChamber.appendChild(title);
                optionWrap = document.createElement('div');
                optionWrap.setAttribute('class', 'optionWrap');
                this.upstreamChamber.appendChild(optionWrap);
                radioArray(optionWrap, ['Absent', 'SCEPTAR', 'PACES', 'SPICE'], ['USCabsent', 'USCSE', 'USCPA', 'USCSP'], 'USC');

                this.downstreamChamber = document.createElement('div');
                this.downstreamChamber.setAttribute('class', 'detectorSection');
                this.detectorForm.appendChild(this.downstreamChamber);
                title = document.createElement('h2');
                title.innerHTML = 'Downstream Chamber';
                this.downstreamChamber.appendChild(title);
                optionWrap = document.createElement('div');
                optionWrap.setAttribute('class', 'optionWrap');
                this.downstreamChamber.appendChild(optionWrap);
                radioArray(optionWrap, ['Absent', 'SCEPTAR', 'ZDS', 'S2', 'S3'], ['DSCabsent', 'DSCSE', 'DSCZD', 'DSCS2', 'DSCS3'], 'DSC');
                document.getElementById('DSC3Label').setAttribute('style', 'display:none');
                document.getElementById('DSC4Label').setAttribute('style', 'display:none');

                this.corona = document.createElement('div');
                this.corona.setAttribute('class', 'detectorSection');
                this.detectorForm.appendChild(this.corona);
                title = document.createElement('h2');
                title.innerHTML = 'Corona';
                this.corona.appendChild(title);
                optionWrap = document.createElement('div');
                optionWrap.setAttribute('class', 'optionWrap');
                this.corona.appendChild(optionWrap);
                radioArray(optionWrap, ['Absent', 'GRIFFIN'], ['coronaAbsent', 'CoronaGR'], 'corona');
                this.corona.appendChild(this.generateGRIFFINtable('corona', [5,6,7,8,9,10,11,12]));

                this.upstreamLampshade = document.createElement('div');
                this.upstreamLampshade.setAttribute('class', 'detectorSection');
                this.detectorForm.appendChild(this.upstreamLampshade);
                title = document.createElement('h2');
                title.innerHTML = 'Upstream Lampshade';
                this.upstreamLampshade.appendChild(title);
                optionWrap = document.createElement('div');
                optionWrap.setAttribute('class', 'optionWrap');
                this.upstreamLampshade.appendChild(optionWrap);
                radioArray(optionWrap, ['Absent', 'GRIFFIN', 'GRIFFIN + DANTE'], ['USLabsent', 'USLGR', 'USLGRDA'], 'USL');
                this.upstreamLampshade.appendChild(this.generateGRIFFINtable('USL', [13,14,15,16]));
                message = document.createElement('p');
                message.setAttribute('id', 'USLmessage');
                message.setAttribute('class', 'message hidden');
                this.upstreamLampshade.appendChild(message);

                this.downstreamLampshade = document.createElement('div');
                this.downstreamLampshade.setAttribute('class', 'detectorSection');
                this.detectorForm.appendChild(this.downstreamLampshade);
                title = document.createElement('h2');
                title.innerHTML = 'Downstream Lampshade';
                this.downstreamLampshade.appendChild(title);
                optionWrap = document.createElement('div');
                optionWrap.setAttribute('class', 'optionWrap');
                this.downstreamLampshade.appendChild(optionWrap);
                radioArray(optionWrap, ['Absent', 'GRIFFIN', 'GRIFFIN + DANTE', 'DESCANT'], ['DSLabsent', 'DSLGR', 'DSLGRDA', 'DSLDS'], 'DSL')
                this.downstreamLampshade.appendChild(this.generateGRIFFINtable('DSL', [1,2,3,4]));

                submit = document.createElement('button');
                submit.setAttribute('class', 'stdin');
                submit.innerHTML = 'Build DAQ Address Table';
                this.detectorForm.appendChild(submit);

                //state management
                //turn appropriate GRIFFIN tables on as needed, and manage SPICE / GRIFFIN constraint in USL:
                document.getElementById('USC0Label').onclick = this.turnOffSPICE;
                document.getElementById('USC1Label').onclick = this.turnOffSPICE;
                document.getElementById('USC2Label').onclick = this.turnOffSPICE;
                document.getElementById('USC3Label').onclick = function(){
                    var message = document.getElementById('USLmessage')

                    message.setAttribute('class', 'message');
                    message.innerHTML = 'SPICE services occupy the upstream lampshade when SPICE is installed; remove SPICE from the upstream chamber to use GRIFFIN here.'

                    document.forms.detectors.USL[0].checked=true;
                    document.getElementById('USL0Label').onclick();

                    document.getElementById('DSC1Label').setAttribute('style', 'display:none');
                    document.getElementById('DSC2Label').setAttribute('style', 'display:none');
                    document.getElementById('DSC3Label').setAttribute('style', 'display:auto');
                    document.getElementById('DSC4Label').setAttribute('style', 'display:auto');
                    document.forms.detectors.DSC[0].checked=true;

                }

                document.getElementById('corona0Label').onclick = function(){
                    document.getElementById('coronaGRIFFINconfig').setAttribute('class', 'GRIFFINconfig hidden');
                }
                document.getElementById('corona1Label').onclick = function(){
                    document.getElementById('coronaGRIFFINconfig').setAttribute('class', 'GRIFFINconfig');
                }

                document.getElementById('USL0Label').onclick = function(){
                    document.getElementById('USLGRIFFINconfig').setAttribute('class', 'GRIFFINconfig hidden');
                }
                document.getElementById('USL1Label').onclick = function(){
                    var USC = document.querySelectorAll('input[name="USC"]:checked')[0]

                    if(USC && USC.value == 'USCSP')
                        return false;

                    document.getElementById('USLGRIFFINconfig').setAttribute('class', 'GRIFFINconfig');
                }

                document.getElementById('DSL0Label').onclick = function(){
                    document.getElementById('DSLGRIFFINconfig').setAttribute('class', 'GRIFFINconfig hidden');
                }
                document.getElementById('DSL1Label').onclick = function(){
                    document.getElementById('DSLGRIFFINconfig').setAttribute('class', 'GRIFFINconfig');
                }
                document.getElementById('DSL3Label').onclick = function(){
                    document.getElementById('DSLGRIFFINconfig').setAttribute('class', 'GRIFFINconfig hidden');
                }

                //defaults
                document.forms.detectors.USC[0].checked=true;
                document.forms.detectors.DSC[0].checked=true;
                document.forms.detectors.corona[1].checked=true;
                document.forms.detectors.USL[1].checked=true;
                document.forms.detectors.DSL[1].checked=true;
            },
            inserted: function() {},
            removed: function() {},
            attributeChanged: function() {}
        },
        events: {

        },
        accessors: {

        },
        methods: {
            'generateGRIFFINtable' : function(section, clovers){
                var table = document.createElement('table'),
                    row, cell, checkbox,
                    i;

                table.setAttribute('class', 'GRIFFINconfig')
                table.setAttribute('id', section+'GRIFFINconfig')

                row = document.createElement('tr');
                table.appendChild(row);
                cell = document.createElement('td');
                cell.innerHTML = 'Clover';
                row.appendChild(cell);
                for(i=0; i<clovers.length; i++){
                    cell = document.createElement('td');
                    cell.innerHTML = clovers[i];
                    row.appendChild(cell);
                }

                row = document.createElement('tr');
                table.appendChild(row);
                cell = document.createElement('td');
                cell.innerHTML = 'Crystal'
                row.appendChild(cell);
                for(i=0; i<clovers.length; i++){
                    cell = document.createElement('td');
                    checkbox = document.createElement('input');
                    checkbox.setAttribute('type', 'checkbox');
                    checkbox.setAttribute('name', 'crystal' + clovers[i]);
                    checkbox.setAttribute('checked', true);
                    cell.appendChild(checkbox);
                    row.appendChild(cell);
                }

                row = document.createElement('tr');
                table.appendChild(row);
                cell = document.createElement('td');
                cell.innerHTML = 'Suppressor'
                row.appendChild(cell);
                for(i=0; i<clovers.length; i++){
                    cell = document.createElement('td');
                    checkbox = document.createElement('input');
                    checkbox.setAttribute('type', 'checkbox');
                    checkbox.setAttribute('name', 'suppressor' + clovers[i]);
                    //checkbox.setAttribute('checked', true);
                    cell.appendChild(checkbox);
                    row.appendChild(cell);
                }

                return table;
            },

            'turnOffSPICE' : function(){
                var message = document.getElementById('USLmessage')
                message.setAttribute('class', 'message hidden');
                document.getElementById('DSC1Label').setAttribute('style', 'display:auto');
                document.getElementById('DSC2Label').setAttribute('style', 'display:auto');
                document.getElementById('DSC3Label').setAttribute('style', 'display:none');
                document.getElementById('DSC4Label').setAttribute('style', 'display:none');
                document.forms.detectors.DSC[0].checked=true;
            }

        }
    });

})();<|endoftext|>


<commit_before>var EventEmitter = require("events").EventEmitter;
var lazy = require("lazy.js");

var AppDispatcher = require("../AppDispatcher");
var AppsEvents = require("../events/AppsEvents");
var appScheme = require("../stores/appScheme");
var AppStatus = require("../constants/AppStatus");
var HealthStatus = require("../constants/HealthStatus");
var TasksEvents = require("../events/TasksEvents");
var TaskStatus = require("../constants/TaskStatus");
var QueueStore = require("./QueueStore");
var QueueEvents = require("../events/QueueEvents");

function removeApp(apps, appId) {
  return lazy(apps).reject({
    id: appId
  }).value();
}

function removeTasks(tasks, relatedAppId, taskIds) {
  return lazy(tasks).reject(function (task) {
    return (taskIds.indexOf(task.id) > -1) && task.appId === relatedAppId;
  }).value();
}

function getTaskHealth(task) {
  var nullResult = true;
  var health = false;
  var healthCheckResults = task.healthCheckResults;

  if (healthCheckResults != null) {
    health = lazy(healthCheckResults).every(function (hcr) {
      if (hcr.firstSuccess) {
        nullResult = false;
        return hcr.alive;
      }

      return false;
    });
  }

  if (!health && nullResult) { // health check has not returned yet
    return HealthStatus.UNKNOWN;
  }

  return health ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY;
}

function setTaskStatus(task) {
  if (task.startedAt != null) {
    task.status = TaskStatus.STARTED;
    task.updatedAt = task.startedAt;
  } else if (task.stagedAt != null) {
    task.status = TaskStatus.STAGED;
    task.updatedAt = task.stagedAt;
  }
}

function getAppHealth(app) {
  var tasksWithUnknownHealth = Math.max(
    app.tasksRunning -
    app.tasksHealthy -
    app.tasksUnhealthy,
    0
  );

  var healthData = [
    {quantity: app.tasksHealthy, state: HealthStatus.HEALTHY},
    {quantity: app.tasksUnhealthy, state: HealthStatus.UNHEALTHY},
    {quantity: tasksWithUnknownHealth, state: HealthStatus.UNKNOWN},
    {quantity: app.tasksStaged, state: HealthStatus.STAGED}
  ];

  // cut off after `instances` many tasks...
  var tasksSum = 0;
  for (var i = 0; i < healthData.length; i++) {
    var capacityLeft = Math.max(0, app.instances - tasksSum);
    tasksSum += healthData[i].quantity;
    healthData[i].quantity = Math.min(capacityLeft, healthData[i].quantity);
  }

  // ... show everything above that in blue
  var overCapacity = Math.max(0, tasksSum - app.instances);

  healthData.push({quantity: overCapacity, state: HealthStatus.OVERCAPACITY});

  // add unscheduled task, or show black if completely suspended
  var isSuspended = app.instances === 0 && tasksSum === 0;
  var unscheduled = Math.max(0, (app.instances - tasksSum));
  var unscheduledOrSuspended = isSuspended ? 1 : unscheduled;

  healthData.push({
    quantity: unscheduledOrSuspended,
    state: HealthStatus.UNSCHEDULED
  });

  return healthData;
}

function getAppHealthWeight(health) {
  return health.reduce(function (weight, state) {
  }, 0);
}

function processApp(app) {
  app = lazy(appScheme).extend(app).value();

  app.status = AppStatus.RUNNING;
  if (app.deployments.length > 0) {
    app.status = AppStatus.DEPLOYING;
  } else if (app.instances === 0 && app.tasksRunning === 0) {
    app.status = AppStatus.SUSPENDED;
  }

  app.health = getAppHealth(app);

  app.tasks = lazy(app.tasks).map(function (task) {
    task.healthStatus = getTaskHealth(task);
    setTaskStatus(task);
    return task;
  }).value();

  return app;
}

function processApps(apps) {
  return lazy(apps).map(function (app) {
    return processApp(app);
  }).value();
}

function applyAppDelayStatus(app, queue) {
  var hasChanges = false;

  var queueEntry = lazy(queue).find(function (entry) {
    return entry.app != null && app.id === entry.app.id && entry.delay != null;
  });

  if (queueEntry) {
    let status;

    if (queueEntry.delay.overdue === false
        && queueEntry.delay.timeLeftSeconds > 0) {
      status = AppStatus.DELAYED;
    } else if (queueEntry.delay.overdue === true) {
      status = AppStatus.WAITING;
    }

    if (status) {
      if (app.status !== status) {
        hasChanges = true;
      }
      app.status = status;
    }
  }

  return hasChanges;
}

function applyAppDelayStatusOnAllApps(apps, queue) {
  var hasChanges = false;
  apps.forEach(function (app) {
    hasChanges = applyAppDelayStatus(app, queue) || hasChanges;
  });
  return hasChanges;
}

var AppsStore = lazy(EventEmitter.prototype).extend({
  // Array of apps objects recieved from the "apps/"-endpoint
  apps: [],
  // Object of the current app recieved from the "apps/[appId]"-endpoint
  // This endpoint delievers more data, like the tasks on the app.
  currentApp: appScheme,

  getCurrentApp: function (appId) {
    if (appId === this.currentApp.id) {
      return this.currentApp;
    }

    var shallowApp = lazy(this.apps).findWhere({id: appId});
    if (shallowApp) {
      return shallowApp;
    }

    return appScheme;
  },

  getTask: function (appId, taskId) {
    return lazy(this.getCurrentApp(appId).tasks).findWhere({"id": taskId});
  }
}).value();

QueueStore.on(QueueEvents.CHANGE, function () {
  var change = applyAppDelayStatusOnAllApps(AppsStore.apps, QueueStore.queue);
  if (change) {
    AppsStore.emit(AppsEvents.CHANGE);
  }
});

AppDispatcher.register(function (action) {
  switch (action.actionType) {
    case AppsEvents.REQUEST_APPS:
      AppsStore.apps = processApps(action.data.body.apps);
      applyAppDelayStatusOnAllApps(AppsStore.apps, QueueStore.queue);
      AppsStore.emit(AppsEvents.CHANGE);
      break;
    case AppsEvents.REQUEST_APPS_ERROR:
      AppsStore.emit(AppsEvents.REQUEST_APPS_ERROR, action.data.body);
      break;
    case AppsEvents.REQUEST_APP:
      AppsStore.currentApp = processApp(action.data.body.app);
      applyAppDelayStatus(AppsStore.currentApp, QueueStore.queue);
      AppsStore.emit(AppsEvents.CHANGE);
      break;
    case AppsEvents.REQUEST_APP_ERROR:
      AppsStore.emit(AppsEvents.REQUEST_APP_ERROR, action.data.body);
      break;
    case AppsEvents.CREATE_APP:
      AppsStore.apps.push(processApp(action.data.body));
      AppsStore.emit(AppsEvents.CREATE_APP);
      AppsStore.emit(AppsEvents.CHANGE);
      break;
    case AppsEvents.CREATE_APP_ERROR:
      AppsStore.emit(
        AppsEvents.CREATE_APP_ERROR,
        action.data.body,
        action.data.status
      );
      break;
    case AppsEvents.DELETE_APP:
      AppsStore.apps =
        removeApp(AppsStore.apps, action.appId);
      AppsStore.emit(AppsEvents.DELETE_APP);
      break;
    case AppsEvents.DELETE_APP_ERROR:
      AppsStore.emit(AppsEvents.DELETE_APP_ERROR, action.data.body);
      break;
    case AppsEvents.RESTART_APP:
      AppsStore.emit(AppsEvents.RESTART_APP);
      break;
    case AppsEvents.RESTART_APP_ERROR:
      AppsStore.emit(AppsEvents.RESTART_APP_ERROR, action.data.body);
      break;
    case AppsEvents.SCALE_APP:
      AppsStore.emit(AppsEvents.SCALE_APP);
      break;
    case AppsEvents.SCALE_APP_ERROR:
      AppsStore.emit(AppsEvents.SCALE_APP_ERROR, action.data.body);
      break;
    case AppsEvents.APPLY_APP:
      AppsStore.emit(AppsEvents.APPLY_APP);
      break;
    case AppsEvents.APPLY_APP_ERROR:
      AppsStore.emit(AppsEvents.APPLY_APP_ERROR, action.data.body);
      break;
    case TasksEvents.DELETE:
      AppsStore.currentApp.tasks =
        removeTasks(
          AppsStore.currentApp.tasks,
          action.appId,
          action.taskIds
        );
      AppsStore.emit(AppsEvents.CHANGE);
      break;
    case TasksEvents.DELETE_ERROR:
      AppsStore.emit(TasksEvents.DELETE_ERROR, action.data.body);
      break;
  }
});

module.exports = AppsStore;
<commit_msg>Adding health weight processing to AppsStore<commit_after>var EventEmitter = require("events").EventEmitter;
var lazy = require("lazy.js");

var AppDispatcher = require("../AppDispatcher");
var AppsEvents = require("../events/AppsEvents");
var appScheme = require("../stores/appScheme");
var AppStatus = require("../constants/AppStatus");
var HealthStatus = require("../constants/HealthStatus");
var TasksEvents = require("../events/TasksEvents");
var TaskStatus = require("../constants/TaskStatus");
var QueueStore = require("./QueueStore");
var QueueEvents = require("../events/QueueEvents");

var healthWeights = {
  [HealthStatus.UNHEALTHY]: 32,
  [HealthStatus.OVERCAPACITY]: 16,
  [HealthStatus.STAGED]: 8,
  [HealthStatus.HEALTHY]: 4,
  [HealthStatus.UNKNOWN]: 2,
  [HealthStatus.UNSCHEDULED]: 1
};

function removeApp(apps, appId) {
  return lazy(apps).reject({
    id: appId
  }).value();
}

function removeTasks(tasks, relatedAppId, taskIds) {
  return lazy(tasks).reject(function (task) {
    return (taskIds.indexOf(task.id) > -1) && task.appId === relatedAppId;
  }).value();
}

function getTaskHealth(task) {
  var nullResult = true;
  var health = false;
  var healthCheckResults = task.healthCheckResults;

  if (healthCheckResults != null) {
    health = lazy(healthCheckResults).every(function (hcr) {
      if (hcr.firstSuccess) {
        nullResult = false;
        return hcr.alive;
      }

      return false;
    });
  }

  if (!health && nullResult) { // health check has not returned yet
    return HealthStatus.UNKNOWN;
  }

  return health ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY;
}

function setTaskStatus(task) {
  if (task.startedAt != null) {
    task.status = TaskStatus.STARTED;
    task.updatedAt = task.startedAt;
  } else if (task.stagedAt != null) {
    task.status = TaskStatus.STAGED;
    task.updatedAt = task.stagedAt;
  }
}

function getAppHealth(app) {
  var tasksWithUnknownHealth = Math.max(
    (app.tasksRunning || 0) -
    (app.tasksHealthy || 0) -
    (app.tasksUnhealthy || 0),
    0
  );

  var healthData = [
    {quantity: app.tasksHealthy || 0, state: HealthStatus.HEALTHY},
    {quantity: app.tasksUnhealthy || 0, state: HealthStatus.UNHEALTHY},
    {quantity: tasksWithUnknownHealth, state: HealthStatus.UNKNOWN},
    {quantity: app.tasksStaged || 0, state: HealthStatus.STAGED}
  ];

  // cut off after `instances` many tasks...
  var tasksSum = 0;
  for (var i = 0; i < healthData.length; i++) {
    var capacityLeft = Math.max(0, app.instances - tasksSum);
    tasksSum += healthData[i].quantity;
    healthData[i].quantity = Math.min(capacityLeft, healthData[i].quantity);
  }

  // ... show everything above that in blue
  var overCapacity = Math.max(0, tasksSum - app.instances);

  healthData.push({quantity: overCapacity, state: HealthStatus.OVERCAPACITY});

  // add unscheduled task, or show black if completely suspended
  var isSuspended = app.instances === 0 && tasksSum === 0;
  var unscheduled = Math.max(0, (app.instances - tasksSum));
  var unscheduledOrSuspended = isSuspended ? 1 : unscheduled;

  healthData.push({
    quantity: unscheduledOrSuspended,
    state: HealthStatus.UNSCHEDULED
  });

  return healthData;
}

function getAppHealthWeight(health) {
  return health.reduce(function (totalWeight, entry) {
    if (entry.quantity) {
      return totalWeight + healthWeights[entry.state];
    }
    return totalWeight;
  }, 0);
}

function processApp(app) {
  app = lazy(appScheme).extend(app).value();

  app.status = AppStatus.RUNNING;
  if (app.deployments.length > 0) {
    app.status = AppStatus.DEPLOYING;
  } else if (app.instances === 0 && app.tasksRunning === 0) {
    app.status = AppStatus.SUSPENDED;
  }

  app.health = getAppHealth(app);
  app.healthWeight = getAppHealthWeight(app.health);

  app.tasks = lazy(app.tasks).map(function (task) {
    task.healthStatus = getTaskHealth(task);
    setTaskStatus(task);
    return task;
  }).value();

  return app;
}

function processApps(apps) {
  return lazy(apps).map(function (app) {
    return processApp(app);
  }).value();
}

function applyAppDelayStatus(app, queue) {
  var hasChanges = false;

  var queueEntry = lazy(queue).find(function (entry) {
    return entry.app != null && app.id === entry.app.id && entry.delay != null;
  });

  if (queueEntry) {
    let status;

    if (queueEntry.delay.overdue === false
        && queueEntry.delay.timeLeftSeconds > 0) {
      status = AppStatus.DELAYED;
    } else if (queueEntry.delay.overdue === true) {
      status = AppStatus.WAITING;
    }

    if (status) {
      if (app.status !== status) {
        hasChanges = true;
      }
      app.status = status;
    }
  }

  return hasChanges;
}

function applyAppDelayStatusOnAllApps(apps, queue) {
  var hasChanges = false;
  apps.forEach(function (app) {
    hasChanges = applyAppDelayStatus(app, queue) || hasChanges;
  });
  return hasChanges;
}

var AppsStore = lazy(EventEmitter.prototype).extend({
  // Array of apps objects recieved from the "apps/"-endpoint
  apps: [],
  // Object of the current app recieved from the "apps/[appId]"-endpoint
  // This endpoint delievers more data, like the tasks on the app.
  currentApp: appScheme,

  getCurrentApp: function (appId) {
    if (appId === this.currentApp.id) {
      return this.currentApp;
    }

    var shallowApp = lazy(this.apps).findWhere({id: appId});
    if (shallowApp) {
      return shallowApp;
    }

    return appScheme;
  },

  getTask: function (appId, taskId) {
    return lazy(this.getCurrentApp(appId).tasks).findWhere({"id": taskId});
  }
}).value();

QueueStore.on(QueueEvents.CHANGE, function () {
  var change = applyAppDelayStatusOnAllApps(AppsStore.apps, QueueStore.queue);
  if (change) {
    AppsStore.emit(AppsEvents.CHANGE);
  }
});

AppDispatcher.register(function (action) {
  switch (action.actionType) {
    case AppsEvents.REQUEST_APPS:
      AppsStore.apps = processApps(action.data.body.apps);
      applyAppDelayStatusOnAllApps(AppsStore.apps, QueueStore.queue);
      AppsStore.emit(AppsEvents.CHANGE);
      break;
    case AppsEvents.REQUEST_APPS_ERROR:
      AppsStore.emit(AppsEvents.REQUEST_APPS_ERROR, action.data.body);
      break;
    case AppsEvents.REQUEST_APP:
      AppsStore.currentApp = processApp(action.data.body.app);
      applyAppDelayStatus(AppsStore.currentApp, QueueStore.queue);
      AppsStore.emit(AppsEvents.CHANGE);
      break;
    case AppsEvents.REQUEST_APP_ERROR:
      AppsStore.emit(AppsEvents.REQUEST_APP_ERROR, action.data.body);
      break;
    case AppsEvents.CREATE_APP:
      AppsStore.apps.push(processApp(action.data.body));
      AppsStore.emit(AppsEvents.CREATE_APP);
      AppsStore.emit(AppsEvents.CHANGE);
      break;
    case AppsEvents.CREATE_APP_ERROR:
      AppsStore.emit(
        AppsEvents.CREATE_APP_ERROR,
        action.data.body,
        action.data.status
      );
      break;
    case AppsEvents.DELETE_APP:
      AppsStore.apps =
        removeApp(AppsStore.apps, action.appId);
      AppsStore.emit(AppsEvents.DELETE_APP);
      break;
    case AppsEvents.DELETE_APP_ERROR:
      AppsStore.emit(AppsEvents.DELETE_APP_ERROR, action.data.body);
      break;
    case AppsEvents.RESTART_APP:
      AppsStore.emit(AppsEvents.RESTART_APP);
      break;
    case AppsEvents.RESTART_APP_ERROR:
      AppsStore.emit(AppsEvents.RESTART_APP_ERROR, action.data.body);
      break;
    case AppsEvents.SCALE_APP:
      AppsStore.emit(AppsEvents.SCALE_APP);
      break;
    case AppsEvents.SCALE_APP_ERROR:
      AppsStore.emit(AppsEvents.SCALE_APP_ERROR, action.data.body);
      break;
    case AppsEvents.APPLY_APP:
      AppsStore.emit(AppsEvents.APPLY_APP);
      break;
    case AppsEvents.APPLY_APP_ERROR:
      AppsStore.emit(AppsEvents.APPLY_APP_ERROR, action.data.body);
      break;
    case TasksEvents.DELETE:
      AppsStore.currentApp.tasks =
        removeTasks(
          AppsStore.currentApp.tasks,
          action.appId,
          action.taskIds
        );
      AppsStore.emit(AppsEvents.CHANGE);
      break;
    case TasksEvents.DELETE_ERROR:
      AppsStore.emit(TasksEvents.DELETE_ERROR, action.data.body);
      break;
  }
});

module.exports = AppsStore;
<|endoftext|>