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

App.stackServiceMapper = App.QuickDataMapper.create({
  model: App.StackService,
  component_model: App.StackServiceComponent,

  config: {
    id: 'service_name',
    service_name: 'service_name',
    display_name: 'display_name',
    config_types: 'config_types',
    comments: 'comments',
    service_version: 'service_version',
    stack_name: 'stack_name',
    stack_version: 'stack_version',
    is_selected: 'is_selected',
    is_installed: 'is_installed',
    required_services: 'required_services',
    service_check_supported: 'service_check_supported',
    service_components_key: 'service_components',
    service_components_type: 'array',
    service_components: {
      item: 'id'
    }
  },

  component_config: {
    id: 'component_name',
    component_name: 'component_name',
    display_name: 'display_name',
    cardinality: 'cardinality',
    custom_commands: 'custom_commands',
    service_name: 'service_name',
    component_category: 'component_category',
    is_master: 'is_master',
    is_client: 'is_client',
    stack_name: 'stack_name',
    stack_version: 'stack_version',
    stack_service_id: 'service_name',
    dependencies_key: 'dependencies',
    dependencies_type: 'array',
    dependencies: {
      item: 'Dependencies.component_name'
    }
  },

  mapStackServices: function(json) {
    this.clearStackModels();
    App.resetDsStoreTypeMap(App.StackServiceComponent);
    App.resetDsStoreTypeMap(App.StackService);
    this.map(json);
  },

  map: function (json) {
    var model = this.get('model');
    var result = [];
    var stackServiceComponents = [];
    this.rearrangeServicesForDisplayOrder(json.items, App.StackService.displayOrder);
    json.items.forEach(function (item) {
      //@TODO: Remove the condition when Flume becomes supported service in any stack
      if (item.StackServices.service_name !== 'FLUME' || App.supports.flume) {
        var stackService = item.StackServices;
        var serviceComponents = [];
        item.serviceComponents.forEach(function (serviceComponent) {
          serviceComponent.StackServiceComponents.id = serviceComponent.StackServiceComponents.component_name;
          serviceComponent.StackServiceComponents.dependencies = serviceComponent.dependencies;
          serviceComponents.push(serviceComponent.StackServiceComponents);
          stackServiceComponents.push(this.parseIt(serviceComponent.StackServiceComponents, this.get('component_config')));
        }, this);
        stackService.service_components = serviceComponents;
        result.push(this.parseIt(stackService, this.get('config')));
      }
    }, this);
    App.store.loadMany(this.get('component_model'), stackServiceComponents);
    App.store.loadMany(model, result);
  },

  /**
   * Clean store from already loaded data.
   **/
  clearStackModels: function () {
    var models = [App.StackServiceComponent, App.StackService];
    models.forEach(function (model) {
      var records = App.get('store').findAll(model).filterProperty('id');
      records.forEach(function (rec) {
        Ember.run(this, function () {
          rec.deleteRecord();
          App.store.commit();
          rec.get('stateManager').transitionTo('loading');
        });
      }, this);
    }, this);
  },

  rearrangeServicesForDisplayOrder: function (array, displayOrderArray) {
    return array.sort(function (a, b) {
      var aValue = displayOrderArray.indexOf(a.StackServices.service_name) != -1 ? displayOrderArray.indexOf(a.StackServices.service_name) : array.length;
      var bValue = displayOrderArray.indexOf(b.StackServices.service_name) != -1 ? displayOrderArray.indexOf(b.StackServices.service_name) : array.length;
      return aValue - bValue;
    });
  }
});

<commit_msg>AMBARI-7052 Unable to load step 1 of Add Service Wizard.  (Max Shepel via ababiichuk)<commit_after>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var App = require('app');

App.stackServiceMapper = App.QuickDataMapper.create({
  model: App.StackService,
  component_model: App.StackServiceComponent,

  config: {
    id: 'service_name',
    service_name: 'service_name',
    display_name: 'display_name',
    config_types: 'config_types',
    comments: 'comments',
    service_version: 'service_version',
    stack_name: 'stack_name',
    stack_version: 'stack_version',
    is_selected: 'is_selected',
    is_installed: 'is_installed',
    required_services: 'required_services',
    service_check_supported: 'service_check_supported',
    service_components_key: 'service_components',
    service_components_type: 'array',
    service_components: {
      item: 'id'
    }
  },

  component_config: {
    id: 'component_name',
    component_name: 'component_name',
    display_name: 'display_name',
    cardinality: 'cardinality',
    custom_commands: 'custom_commands',
    service_name: 'service_name',
    component_category: 'component_category',
    is_master: 'is_master',
    is_client: 'is_client',
    stack_name: 'stack_name',
    stack_version: 'stack_version',
    stack_service_id: 'service_name',
    dependencies_key: 'dependencies',
    dependencies_type: 'array',
    dependencies: {
      item: 'Dependencies.component_name'
    }
  },

  mapStackServices: function(json) {
    this.clearStackModels();
    App.resetDsStoreTypeMap(App.StackServiceComponent);
    App.resetDsStoreTypeMap(App.StackService);
    this.map(json);
  },

  map: function (json) {
    var model = this.get('model');
    var result = [];
    var stackServiceComponents = [];
    this.rearrangeServicesForDisplayOrder(json.items, App.StackService.displayOrder);
    json.items.forEach(function (item) {
      //@TODO: Remove the condition when Flume becomes supported service in any stack
      if (item.StackServices.service_name !== 'FLUME' || App.supports.flume) {
        var stackService = item.StackServices;
        var serviceComponents = [];
        item.serviceComponents.forEach(function (serviceComponent) {
          serviceComponent.StackServiceComponents.id = serviceComponent.StackServiceComponents.component_name;
          serviceComponent.StackServiceComponents.dependencies = serviceComponent.dependencies;
          serviceComponents.push(serviceComponent.StackServiceComponents);
          stackServiceComponents.push(this.parseIt(serviceComponent.StackServiceComponents, this.get('component_config')));
        }, this);
        stackService.service_components = serviceComponents;
        result.push(this.parseIt(stackService, this.get('config')));
      }
    }, this);
    App.store.loadMany(this.get('component_model'), stackServiceComponents);
    App.store.loadMany(model, result);
  },

  /**
   * Clean store from already loaded data.
   **/
  clearStackModels: function () {
    var models = [App.StackServiceComponent, App.StackService];
    models.forEach(function (model) {
      var records = App.get('store').findAll(model).filterProperty('id');
      records.forEach(function (rec) {
        Ember.run(this, function () {
          rec.deleteRecord();
          App.store.commit();
        });
      }, this);
    }, this);
  },

  rearrangeServicesForDisplayOrder: function (array, displayOrderArray) {
    return array.sort(function (a, b) {
      var aValue = displayOrderArray.indexOf(a.StackServices.service_name) != -1 ? displayOrderArray.indexOf(a.StackServices.service_name) : array.length;
      var bValue = displayOrderArray.indexOf(b.StackServices.service_name) != -1 ? displayOrderArray.indexOf(b.StackServices.service_name) : array.length;
      return aValue - bValue;
    });
  }
});

<|endoftext|>


<commit_before>/*!
* HEX <=> RGB Conversion
* Copyright(c) 2011 Daniel Lamb <daniellmb.com>
* MIT Licensed
*/

(function (context) {

    context['toRGB'] = function (/* String */color) {
        // summary:
        //		Converts a 6 digit Hexadecimal string value to an RGB integer array.
        //      Important! input must be a 6 digit Hexadecimal string "bad" will
        //      not convert correctly but "bbaadd" will. To keep the function as
        //      light as possible there is no idiot-proofing, if you pass in bad
        //      data I'm not fixing it for you :-)
        //
	    // color: `String`
        //      6 digit Hexadecimal string value
        //
        // returns: Array
        //		An array containing the RGB integers in the following format [red, green, blue]
        //
        // example:
        //		Convert the Hexadecimal value "FF0000" (red color) to RGB integers.
        //      The variable rgb value will be equal to [255, 0, 0]
        //
        //		var rgb = toRGB("FF0000");

        //convert string to base 16 number
        var num = parseInt(color, 16);

        //return the red, green and blue values as a new array
        return [num >> 16, num >> 8 & 255, num & 255];
    };

    context['toHex'] = function (/* Number */red, /* Number */green, /* Number */blue) {
        // summary:
        //		Converts 3 RGB integer values into a Hexadecimal string.
        //      Important! input must be integers with a range of 0 to 255.
        //      To keep the function as light as possible there is no idiot-proofing,
        //      if you pass in bad data I'm not fixing it for you :-)
        //
        // red: Number
        //		number ranging from 0 to 255 indicating the amount of red
        // green: Number
        //		number ranging from 0 to 255 indicating the amount of green
        // blue: Number
        //		number ranging from 0 to 255 indicating the amount of blue
        //
        // returns: String
        //		6 digit Hexadecimal string value
        //
        // example:
        //		var hex = toHex(255, 0, 0);

        return (blue | green << 8 | red << 16).toString(16);
    };

})(this);<commit_msg>fixed formatting of source version<commit_after>/*!
* HEX <=> RGB Conversion
* Copyright(c) 2011 Daniel Lamb <daniellmb.com>
* MIT Licensed
*/

(function (context) {

    context['toRGB'] = function (/* String */color) {
        // summary:
        //		Converts a 6 digit Hexadecimal string value to an RGB integer array.
        //      Important! input must be a 6 digit Hexadecimal string "bad" will
        //      not convert correctly but "bbaadd" will. To keep the function as
        //      light as possible there is no idiot-proofing, if you pass in bad
        //      data I'm not fixing it for you :-)
        //
        // color: `String`
        //      6 digit Hexadecimal string value
        //
        // returns: Array
        //		An array containing the RGB integers in the following format [red, green, blue]
        //
        // example:
        //		Convert the Hexadecimal value "FF0000" (red color) to RGB integers.
        //      The variable rgb value will be equal to [255, 0, 0]
        //
        //		var rgb = toRGB("FF0000");

        //convert string to base 16 number
        var num = parseInt(color, 16);

        //return the red, green and blue values as a new array
        return [num >> 16, num >> 8 & 255, num & 255];
    };

    context['toHex'] = function (/* Number */red, /* Number */green, /* Number */blue) {
        // summary:
        //		Converts 3 RGB integer values into a Hexadecimal string.
        //      Important! input must be integers with a range of 0 to 255.
        //      To keep the function as light as possible there is no idiot-proofing,
        //      if you pass in bad data I'm not fixing it for you :-)
        //
        // red: Number
        //		number ranging from 0 to 255 indicating the amount of red
        // green: Number
        //		number ranging from 0 to 255 indicating the amount of green
        // blue: Number
        //		number ranging from 0 to 255 indicating the amount of blue
        //
        // returns: String
        //		6 digit Hexadecimal string value
        //
        // example:
        //		var hex = toHex(255, 0, 0);

        return (blue | green << 8 | red << 16).toString(16);
    };

})(this);<|endoftext|>


<commit_before>'use strict';

const shortid = require('shortid');

const DEFAULT_STATE         = 'queued';
const DEFAULT_TEMPLATE      = 'template.aep';
const DEFAULT_COMPOSITION   = 'comp1';
const DEFAULT_PROJECT_TYPE  = 'default';

const TICKER_INTERVAL       = 60 * 1000 || process.env.API_UPDATE_INTERVAL; // 1 minute

const AE_OUTPUT_MODULE      = process.env.AE_OUTPUT_MODULE || 'h264';
const AE_OUTPUT_EXT         = 'mp4';

class Project {

    /**
     * Creates project entity from params
     * @param  {Object}
     * @param  {Object} api Inject connected api
     * @return {Project}
     */
    constructor(params, api) {
        this.deserialize( params );

        this.callbacks  = {};
        this.ticker     = null;
        this.api        = api || null;
        this.error      = null;
    }

    /**
     * Serialize project properties to plain object
     * @return {Object}
     */
    serialize() {
        return {
            uid:            this.uid,
            type:           this.type,
            state:          this.state,
            assets:         this.assets,
            template:       this.template,
            settings:       this.settings,
            composition:    this.composition,
            actions:        this.actions,
            errorMessage:   this.errorMessage
        };
    }

    /**
     * Desirialize data from plain object to Project object
     * @param  {Object} params
     */
    deserialize(params) {
        let data            = params            || {};

        this.uid            = data.uid          || shortid();
        this.state          = data.state        || DEFAULT_STATE;
        this.template       = data.template     || DEFAULT_TEMPLATE;
        this.composition    = data.composition  || DEFAULT_COMPOSITION;
        this.type           = data.type         || DEFAULT_PROJECT_TYPE;
        this.assets         = data.assets       || [];
        this.actions        = data.actions      || [];
        this.settings       = data.settings     || { outputModule: AE_OUTPUT_MODULE, outputExt: AE_OUTPUT_EXT };
        this.errorMessage   = data.errorMessage || null;

        return this;
    }

    /**
     * Sets project state
     * @private
     * @param {String} state
     */
    setStateAndSave(err, state) {
        return new Promise((resolve, reject) => {
            // chage state
            this.state = state;

            // call inner method (for project entity created on renderer side)
            this.callMethod( state );

            // save entity and resolve promise
            this.save().then(() => {
                resolve(this);
            });
        });
    }

    /**
     * Sets state of project to 'rendering' (render started)
     * @return {Promise}
     */
    prepare() {
        return this.setStateAndSave('rendering');
    }

    /**
     * Sets state of project to 'finished' (render succeeded)
     * @return {Promise}
     */
    finish() {
        return this.setStateAndSave('finished');
    }

    /**
     * Sets state of project to 'error' (render failed)
     * @param {Mixed} err
     * @return {Promise}
     */
    failure(err) {
        this.errorMessage = (err.message) ? err.message : err;;
        return this.setStateAndSave('failed');
    }

    /**
     * Function get called every TICKER_INTERVAL
     * to check project state on server
     */
    onTick() {
        if (this.api === null) return;

        this.api.get(this.uid).then((project) => {
            if (this.state !== project.state) {
                this.deserialize( project );
                this.callMethod( project.state );
            }
        })
    }

    /**
     * Binding for api update method
     * @return {Promise}
     */
    save() {
        return (this.api !== null) ? this.api.update(this) : new Promise(r => r());
    }

    /**
     * Binding for api remove method
     * @return {Promise}
     */
    remove() {
        return (this.api !== null) ? this.api.remove(this) : new Promise(r => r());
    }

    /**
     * Binder for events
     * @param  {String}   method   Event name
     * @param  {Function} callback Event handelr
     * @return {[type]}            [description]
     * TODO: get rid of setInterval memory leaking
     */
    on(method, callback) {
        if (!this.ticker && this.api !== null) {
            this.ticker = setInterval(() => { this.onTick(); }, TICKER_INTERVAL);
        }

        if (this.callbacks[method]) {
            return this.callbacks[ method ].push( callback );
        }

        return this.callbacks[ method ] = [ callback ]
    }

    /**
     * Event caller
     * @param  {String} method Event name
     */
    callMethod(method) {
        if (this.callbacks[method]) {
            for (let callback of this.callbacks[method]) {
                callback( this.errorMessage, this);
            }
        }
    }
}

module.exports = Project;
<commit_msg>Fix for issue #19<commit_after>'use strict';

const shortid = require('shortid');

const DEFAULT_STATE         = 'queued';
const DEFAULT_TEMPLATE      = 'template.aep';
const DEFAULT_COMPOSITION   = 'comp1';
const DEFAULT_PROJECT_TYPE  = 'default';

const TICKER_INTERVAL       = 60 * 1000 || process.env.API_UPDATE_INTERVAL; // 1 minute

const AE_OUTPUT_MODULE      = process.env.AE_OUTPUT_MODULE || 'h264';
const AE_OUTPUT_EXT         = 'mp4';

class Project {

    /**
     * Creates project entity from params
     * @param  {Object}
     * @param  {Object} api Inject connected api
     * @return {Project}
     */
    constructor(params, api) {
        this.deserialize( params );

        this.callbacks  = {};
        this.ticker     = null;
        this.api        = api || null;
        this.error      = null;
    }

    /**
     * Serialize project properties to plain object
     * @return {Object}
     */
    serialize() {
        return {
            uid:            this.uid,
            type:           this.type,
            state:          this.state,
            assets:         this.assets,
            template:       this.template,
            settings:       this.settings,
            composition:    this.composition,
            actions:        this.actions,
            errorMessage:   this.errorMessage
        };
    }

    /**
     * Desirialize data from plain object to Project object
     * @param  {Object} params
     */
    deserialize(params) {
        let data            = params            || {};

        this.uid            = data.uid          || shortid();
        this.state          = data.state        || DEFAULT_STATE;
        this.template       = data.template     || DEFAULT_TEMPLATE;
        this.composition    = data.composition  || DEFAULT_COMPOSITION;
        this.type           = data.type         || DEFAULT_PROJECT_TYPE;
        this.assets         = data.assets       || [];
        this.actions        = data.actions      || [];
        this.settings       = data.settings     || { outputModule: AE_OUTPUT_MODULE, outputExt: AE_OUTPUT_EXT };
        this.errorMessage   = data.errorMessage || null;

        return this;
    }

    /**
     * Sets project state
     * @private
     * @param {String} state
     */
    setStateAndSave(state) {
        return new Promise((resolve, reject) => {
            // chage state
            this.state = state;

            // call inner method (for project entity created on renderer side)
            this.callMethod( state );

            // save entity and resolve promise
            this.save().then(() => {
                resolve(this);
            });
        });
    }

    /**
     * Sets state of project to 'rendering' (render started)
     * @return {Promise}
     */
    prepare() {
        return this.setStateAndSave('rendering');
    }

    /**
     * Sets state of project to 'finished' (render succeeded)
     * @return {Promise}
     */
    finish() {
        return this.setStateAndSave('finished');
    }

    /**
     * Sets state of project to 'error' (render failed)
     * @param {Mixed} err
     * @return {Promise}
     */
    failure(err) {
        this.errorMessage = (err.message) ? err.message : err;;
        return this.setStateAndSave('failed');
    }

    /**
     * Function get called every TICKER_INTERVAL
     * to check project state on server
     */
    onTick() {
        if (this.api === null) return;

        this.api.get(this.uid).then((project) => {
            if (this.state !== project.state) {
                this.deserialize( project );
                this.callMethod( project.state );
            }
        })
    }

    /**
     * Binding for api update method
     * @return {Promise}
     */
    save() {
        return (this.api !== null) ? this.api.update(this) : new Promise(r => r());
    }

    /**
     * Binding for api remove method
     * @return {Promise}
     */
    remove() {
        return (this.api !== null) ? this.api.remove(this) : new Promise(r => r());
    }

    /**
     * Binder for events
     * @param  {String}   method   Event name
     * @param  {Function} callback Event handelr
     * @return {[type]}            [description]
     * TODO: get rid of setInterval memory leaking
     */
    on(method, callback) {
        if (!this.ticker && this.api !== null) {
            this.ticker = setInterval(() => { this.onTick(); }, TICKER_INTERVAL);
        }

        if (this.callbacks[method]) {
            return this.callbacks[ method ].push( callback );
        }

        return this.callbacks[ method ] = [ callback ]
    }

    /**
     * Event caller
     * @param  {String} method Event name
     */
    callMethod(method) {
        if (this.callbacks[method]) {
            for (let callback of this.callbacks[method]) {
                callback( this.errorMessage, this);
            }
        }
    }
}

module.exports = Project;
<|endoftext|>


<commit_before>import React from 'react';
import cx from 'classnames';
import { props, t, skinnable, pure } from '../utils';
import PanelHeader from './PanelHeader';
import capitalize from 'lodash/capitalize';
import LoadingSpinner from '../loading-spinner/LoadingSpinner';
import FlexView from '../flex/FlexView';

import './panel.scss';

export const Props = {
  type: t.enums.of(['docked-top', 'docked-left', 'docked-right', 'docked-bottom', 'floating']),
  header: t.maybe(t.struct({
    collapse: t.maybe(t.struct({
      direction: t.enums.of(['up', 'left', 'right', 'down']),
      onExpand: t.Func,
      onCollapse: t.Func,
      isCollapsed: t.maybe(t.Bool)
    })),
    content: t.maybe(t.ReactChildren),
    title: t.maybe(t.ReactChildren),
    hideTitleWhenExpanded: t.maybe(t.Bool),
    menu: t.maybe(t.ReactChildren)
  })),
  loading: t.maybe(t.Bool),
  dark: t.maybe(t.Bool),
  softLoading: t.maybe(t.Bool),
  softLoadingDelay: t.maybe(t.refinement(t.Num, v => v >= 0, 'NonNegativeNumber')),
  children: t.ReactChildren,
  className: t.maybe(t.Str),
  clearMargin: t.maybe(t.enums.of(['top', 'left', 'right', 'bottom'])),
  style: t.maybe(t.Obj)
};

@pure
@skinnable()
@props(Props)
export default class Panel extends React.Component {
  static defaultProps = {
    style: {},
    loading: false,
    softLoading: false,
    softLoadingDelay: 0,
    dark: false
  };

  _softLoadingActive = false;
  _softLoadingTimer = null;

  updateSoftLoadingState({ softLoading: prevSoftLoading }) {
    const { softLoading, softLoadingDelay } = this.props;
    if (!softLoading) {
      if (this._softLoadingTimer) {
        clearTimeout(this._softLoadingTimer);
      }
      this._softLoadingActive = false;
      if (prevSoftLoading) {
        this.forceUpdate();
      }
    } else if (!prevSoftLoading && softLoading && softLoadingDelay > 0) { // delay === 0 is handled without timer
      this._softLoadingTimer = setTimeout(() => {
        this._softLoadingActive = true;
        this.forceUpdate();
      }, softLoadingDelay);
    }
  }

  isSoftLoading({ softLoading, softLoadingDelay }) {
    return this._softLoadingActive || (softLoading && softLoadingDelay === 0); // optimization for the default delay === 0;
  }

  componentDidMount() {
    this.updateSoftLoadingState({ softLoading: false });
  }

  componentDidUpdate(oldProps) {
    this.updateSoftLoadingState(oldProps);
  }

  componentWillUnmount() {
    if (this._softLoadingTimer) {
      clearTimeout(this._softLoadingTimer);
    }
  }

  toggleExpanded = () => {
    const { isCollapsed, onExpand, onCollapse } = this.props.header.collapse;
    if (isCollapsed) {
      onExpand();
    } else {
      onCollapse();
    }
  };

  getStyle = () => {
    const style = {};
    if (this.props.clearMargin) {
      const marginToClear = `margin${capitalize(this.props.clearMargin)}`;
      style[marginToClear] = 0;
    }
    return {
      ...this.props.style,
      ...style
    };
  };

  getLocals() {
    const { header, children, loading, softLoading, softLoadingDelay, type, className, dark } = this.props;
    const collapsable = header && header.collapse;
    const isExpanded = !collapsable || !header.collapse.isCollapsed;
    const panelState = isExpanded ? 'expanded' : 'collapsed';
    const directionClass = collapsable ? (`collapse-${header.collapse.direction}`) : '';
    const verticalDirection = collapsable && (collapsable.direction === 'up' || collapsable.direction === 'down');
    const themeType = dark ? 'is-dark' : 'is-light';

    return {
      header,
      className: cx('panel', type, { collapsable }, themeType, directionClass, panelState, className),
      style: this.getStyle(),
      isExpanded,
      toggleExpanded: this.toggleExpanded,
      verticalDirection,
      children,
      loading,
      softLoading: this.isSoftLoading({ softLoading, softLoadingDelay })
    };
  }

  templateSoftLoading = ({ softLoading, isExpanded }) => {
    return softLoading && isExpanded ? (

    ) : null;
  };

  templateHeader = ({ header, isExpanded, toggleExpanded }) => {
    return (
      header ?
        <PanelHeader
          title={header.hideTitleWhenExpanded && isExpanded ? undefined : header.title}
          content={header.content}
          menu={header.menu}
          collapse={header.collapse ? {
            direction: header.collapse.direction,
            isExpanded,
            onToggleExpanded: toggleExpanded
          } : null}
        />
    : null );
  };

  templateExpandedContent = ({ children, loading }) => {
    return (
      <FlexView className='panel-content' column grow shrink={false} style={{ position: 'relative' }}>
        {children}
        {loading && <LoadingSpinner />}
      </FlexView>
    );
  };

  templateCollapsedContent = ({ header, verticalDirection }) => {
    return (
      (!verticalDirection && header && header.title) ?
        <FlexView className='panel-content' column grow>
          <FlexView grow className='panel-content-title'>
            {header.title}
          </FlexView>
        </FlexView>
        : null
    );
  };

  template({ header, children, loading, softLoading, className, style, isExpanded, toggleExpanded, verticalDirection }) {
    return (
      <FlexView className={className} grow style={style} column>
        {this.templateSoftLoading({ softLoading, isExpanded })}
        {this.templateHeader({ header, isExpanded, toggleExpanded })}
        {isExpanded ?
          this.templateExpandedContent({ children, loading }) :
          this.templateCollapsedContent({ header, verticalDirection })
        }
      </FlexView>
    );
  }

}
<commit_msg>Panel shouldnt import its style<commit_after>import React from 'react';
import cx from 'classnames';
import { props, t, skinnable, pure } from '../utils';
import PanelHeader from './PanelHeader';
import capitalize from 'lodash/capitalize';
import LoadingSpinner from '../loading-spinner/LoadingSpinner';
import FlexView from '../flex/FlexView';

export const Props = {
  type: t.enums.of(['docked-top', 'docked-left', 'docked-right', 'docked-bottom', 'floating']),
  header: t.maybe(t.struct({
    collapse: t.maybe(t.struct({
      direction: t.enums.of(['up', 'left', 'right', 'down']),
      onExpand: t.Func,
      onCollapse: t.Func,
      isCollapsed: t.maybe(t.Bool)
    })),
    content: t.maybe(t.ReactChildren),
    title: t.maybe(t.ReactChildren),
    hideTitleWhenExpanded: t.maybe(t.Bool),
    menu: t.maybe(t.ReactChildren)
  })),
  loading: t.maybe(t.Bool),
  dark: t.maybe(t.Bool),
  softLoading: t.maybe(t.Bool),
  softLoadingDelay: t.maybe(t.refinement(t.Num, v => v >= 0, 'NonNegativeNumber')),
  children: t.ReactChildren,
  className: t.maybe(t.Str),
  clearMargin: t.maybe(t.enums.of(['top', 'left', 'right', 'bottom'])),
  style: t.maybe(t.Obj)
};

@pure
@skinnable()
@props(Props)
export default class Panel extends React.Component {
  static defaultProps = {
    style: {},
    loading: false,
    softLoading: false,
    softLoadingDelay: 0,
    dark: false
  };

  _softLoadingActive = false;
  _softLoadingTimer = null;

  updateSoftLoadingState({ softLoading: prevSoftLoading }) {
    const { softLoading, softLoadingDelay } = this.props;
    if (!softLoading) {
      if (this._softLoadingTimer) {
        clearTimeout(this._softLoadingTimer);
      }
      this._softLoadingActive = false;
      if (prevSoftLoading) {
        this.forceUpdate();
      }
    } else if (!prevSoftLoading && softLoading && softLoadingDelay > 0) { // delay === 0 is handled without timer
      this._softLoadingTimer = setTimeout(() => {
        this._softLoadingActive = true;
        this.forceUpdate();
      }, softLoadingDelay);
    }
  }

  isSoftLoading({ softLoading, softLoadingDelay }) {
    return this._softLoadingActive || (softLoading && softLoadingDelay === 0); // optimization for the default delay === 0;
  }

  componentDidMount() {
    this.updateSoftLoadingState({ softLoading: false });
  }

  componentDidUpdate(oldProps) {
    this.updateSoftLoadingState(oldProps);
  }

  componentWillUnmount() {
    if (this._softLoadingTimer) {
      clearTimeout(this._softLoadingTimer);
    }
  }

  toggleExpanded = () => {
    const { isCollapsed, onExpand, onCollapse } = this.props.header.collapse;
    if (isCollapsed) {
      onExpand();
    } else {
      onCollapse();
    }
  };

  getStyle = () => {
    const style = {};
    if (this.props.clearMargin) {
      const marginToClear = `margin${capitalize(this.props.clearMargin)}`;
      style[marginToClear] = 0;
    }
    return {
      ...this.props.style,
      ...style
    };
  };

  getLocals() {
    const { header, children, loading, softLoading, softLoadingDelay, type, className, dark } = this.props;
    const collapsable = header && header.collapse;
    const isExpanded = !collapsable || !header.collapse.isCollapsed;
    const panelState = isExpanded ? 'expanded' : 'collapsed';
    const directionClass = collapsable ? (`collapse-${header.collapse.direction}`) : '';
    const verticalDirection = collapsable && (collapsable.direction === 'up' || collapsable.direction === 'down');
    const themeType = dark ? 'is-dark' : 'is-light';

    return {
      header,
      className: cx('panel', type, { collapsable }, themeType, directionClass, panelState, className),
      style: this.getStyle(),
      isExpanded,
      toggleExpanded: this.toggleExpanded,
      verticalDirection,
      children,
      loading,
      softLoading: this.isSoftLoading({ softLoading, softLoadingDelay })
    };
  }

  templateSoftLoading = ({ softLoading, isExpanded }) => {
    return softLoading && isExpanded ? (

    ) : null;
  };

  templateHeader = ({ header, isExpanded, toggleExpanded }) => {
    return (
      header ?
        <PanelHeader
          title={header.hideTitleWhenExpanded && isExpanded ? undefined : header.title}
          content={header.content}
          menu={header.menu}
          collapse={header.collapse ? {
            direction: header.collapse.direction,
            isExpanded,
            onToggleExpanded: toggleExpanded
          } : null}
        />
    : null );
  };

  templateExpandedContent = ({ children, loading }) => {
    return (
      <FlexView className='panel-content' column grow shrink={false} style={{ position: 'relative' }}>
        {children}
        {loading && <LoadingSpinner />}
      </FlexView>
    );
  };

  templateCollapsedContent = ({ header, verticalDirection }) => {
    return (
      (!verticalDirection && header && header.title) ?
        <FlexView className='panel-content' column grow>
          <FlexView grow className='panel-content-title'>
            {header.title}
          </FlexView>
        </FlexView>
        : null
    );
  };

  template({ header, children, loading, softLoading, className, style, isExpanded, toggleExpanded, verticalDirection }) {
    return (
      <FlexView className={className} grow style={style} column>
        {this.templateSoftLoading({ softLoading, isExpanded })}
        {this.templateHeader({ header, isExpanded, toggleExpanded })}
        {isExpanded ?
          this.templateExpandedContent({ children, loading }) :
          this.templateCollapsedContent({ header, verticalDirection })
        }
      </FlexView>
    );
  }

}
<|endoftext|>


<commit_before>import React from 'react';
import { findDOMNode } from 'react-dom';
import { mount, shallow } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';
import CustomProperties from '../../components/custom-properties';
import * as utilities from '../../utilities';

const FAKE_ROOT_NODE = {
  style: {
    setProperty: () => {},
    removeProperty: () => {},
  },
};

describe('<CustomProperties />', () => {
  before(() => {
    sinon.spy(utilities, 'setStyleProperty');
    sinon.spy(utilities, 'removeStyleProperty');
    sinon.stub(utilities, 'getRoot').returns(FAKE_ROOT_NODE);
  });

  beforeEach(() => {
    utilities.setStyleProperty.reset();
    utilities.removeStyleProperty.reset();
  });

  it('renders it`s children', () => {
    const wrapper = shallow(
      <CustomProperties>Hey</CustomProperties>
    );

    expect(wrapper.text()).to.equal('Hey');
  });

  describe('on mount', () => {
    it('sets the correct custom properties on it`s container', () => {
      const wrapper = mount(
        <CustomProperties properties={{
          '--foo': 'bar',
          '--baz': 'bat',
        }} />
      );

      const container = wrapper.instance().container;

      expect(utilities.setStyleProperty.callCount).to.equal(2);
      expect(utilities.setStyleProperty.calledWith(container, '--foo', 'bar')).to.equal(true);
      expect(utilities.setStyleProperty.calledWith(container, '--baz', 'bat')).to.equal(true);
    });
  });

  describe('when the properties are updated', () => {
    it('sets only the custom properties that have changed', () => {
      const wrapper = mount(
        <CustomProperties properties={{
          '--foo': 'bar',
          '--baz': 'bat',
        }} />
      );

      const container = wrapper.instance().container;

      utilities.setStyleProperty.reset();

      wrapper.setProps({
        properties: {
          '--foo': 'bar',
          '--baz': 'updated',
          '--new': 'value',
        }
      });

      expect(utilities.setStyleProperty.callCount).to.equal(2);
      expect(utilities.setStyleProperty.calledWith(container, '--baz', 'updated')).to.equal(true);
      expect(utilities.setStyleProperty.calledWith(container, '--new', 'value')).to.equal(true);
    });

    it('removes any custom properties that are no longer present', () => {
      const wrapper = mount(
        <CustomProperties properties={{
          '--foo': 'bar',
          '--baz': 'bat',
        }} />
      );

      const container = wrapper.instance().container;

      wrapper.setProps({
        properties: {
          '--foo': 'bar',
        }
      });

      expect(utilities.removeStyleProperty.callCount).to.equal(1);
      expect(utilities.removeStyleProperty.calledWith(container, '--baz')).to.equal(true);
    });
  });

  describe('when the global flag is passed', () => {
    describe('on mount', () => {
      it('sets the correct custom properties on the document root', () => {
        mount(
          <CustomProperties
            properties={{ '--foo': 'bar', '--baz': 'bat' }}
            global
          />
        );

        expect(utilities.setStyleProperty.callCount).to.equal(2);
        expect(utilities.setStyleProperty.calledWith(FAKE_ROOT_NODE, '--foo', 'bar')).to.equal(true);
        expect(utilities.setStyleProperty.calledWith(FAKE_ROOT_NODE, '--baz', 'bat')).to.equal(true);
      });
    });

    describe('on unmount', () => {
      it('removes all the current style properties from the document root', () => {
        const wrapper = mount(
          <CustomProperties
            properties={{ '--foo': 'bar', '--baz': 'bat' }}
            global
          />
        );

        wrapper.unmount();

        expect(utilities.removeStyleProperty.callCount).to.equal(2);
        expect(utilities.removeStyleProperty.calledWith(FAKE_ROOT_NODE, '--foo')).to.equal(true);
        expect(utilities.removeStyleProperty.calledWith(FAKE_ROOT_NODE, '--baz')).to.equal(true);
      });
    });

    describe('when the properties are updated', () => {
      it('sets only the custom properties that have changed', () => {
        const wrapper = mount(
          <CustomProperties
            properties={{ '--foo': 'bar', '--baz': 'bat' }}
            global
          />
        );

        utilities.setStyleProperty.reset();

        wrapper.setProps({
          properties: {
            '--foo': 'bar',
            '--baz': 'updated',
            '--new': 'value',
          }
        });

        expect(utilities.setStyleProperty.callCount).to.equal(2);
        expect(utilities.setStyleProperty.calledWith(FAKE_ROOT_NODE, '--baz', 'updated')).to.equal(true);
        expect(utilities.setStyleProperty.calledWith(FAKE_ROOT_NODE, '--new', 'value')).to.equal(true);
      });

      it('removes any custom properties that are no longer present', () => {
        const wrapper = mount(
          <CustomProperties
            properties={{ '--foo': 'bar', '--baz': 'bat' }}
            global
          />
        );

        wrapper.setProps({
          properties: {
            '--foo': 'bar',
          }
        });

        expect(utilities.removeStyleProperty.callCount).to.equal(1);
        expect(utilities.removeStyleProperty.calledWith(FAKE_ROOT_NODE, '--baz')).to.equal(true);
      });
    });
  });
});
<commit_msg>Add tests.<commit_after>import React from 'react';
import { findDOMNode } from 'react-dom';
import { mount, shallow } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';
import CustomProperties from '../../components/custom-properties';
import * as utilities from '../../utilities';

const FAKE_ROOT_NODE = {
  style: {
    setProperty: () => {},
    removeProperty: () => {},
  },
};

describe('<CustomProperties />', () => {
  before(() => {
    sinon.spy(utilities, 'setStyleProperty');
    sinon.spy(utilities, 'removeStyleProperty');
    sinon.stub(utilities, 'getRoot').returns(FAKE_ROOT_NODE);
  });

  beforeEach(() => {
    utilities.setStyleProperty.reset();
    utilities.removeStyleProperty.reset();
  });

  it('renders it`s children', () => {
    const wrapper = shallow(
      <CustomProperties>Hey</CustomProperties>
    );

    expect(wrapper.text()).to.equal('Hey');
  });

  describe('on mount', () => {
    it('sets the correct custom properties on it`s container', () => {
      const wrapper = mount(
        <CustomProperties properties={{
          '--foo': 'bar',
          '--baz': 'bat',
        }} />
      );

      const container = wrapper.instance().container;

      expect(utilities.setStyleProperty.callCount).to.equal(2);
      expect(utilities.setStyleProperty.calledWith(container, '--foo', 'bar')).to.equal(true);
      expect(utilities.setStyleProperty.calledWith(container, '--baz', 'bat')).to.equal(true);
    });
  });

  describe('when the properties are updated', () => {
    it('sets only the custom properties that have changed', () => {
      const wrapper = mount(
        <CustomProperties properties={{
          '--foo': 'bar',
          '--baz': 'bat',
        }} />
      );

      const container = wrapper.instance().container;

      utilities.setStyleProperty.reset();

      wrapper.setProps({
        properties: {
          '--foo': 'bar',
          '--baz': 'updated',
          '--new': 'value',
        }
      });

      expect(utilities.setStyleProperty.callCount).to.equal(2);
      expect(utilities.setStyleProperty.calledWith(container, '--baz', 'updated')).to.equal(true);
      expect(utilities.setStyleProperty.calledWith(container, '--new', 'value')).to.equal(true);
    });

    it('removes any custom properties that are no longer present', () => {
      const wrapper = mount(
        <CustomProperties properties={{
          '--foo': 'bar',
          '--baz': 'bat',
        }} />
      );

      const container = wrapper.instance().container;

      wrapper.setProps({
        properties: {
          '--foo': 'bar',
        }
      });

      expect(utilities.removeStyleProperty.callCount).to.equal(1);
      expect(utilities.removeStyleProperty.calledWith(container, '--baz')).to.equal(true);
    });
  });

  describe('when the global flag is passed', () => {
    describe('on mount', () => {
      it('sets the correct custom properties on the document root', () => {
        mount(
          <CustomProperties
            properties={{ '--foo': 'bar', '--baz': 'bat' }}
            global
          />
        );

        expect(utilities.setStyleProperty.callCount).to.equal(2);
        expect(utilities.setStyleProperty.calledWith(FAKE_ROOT_NODE, '--foo', 'bar')).to.equal(true);
        expect(utilities.setStyleProperty.calledWith(FAKE_ROOT_NODE, '--baz', 'bat')).to.equal(true);
      });

      it('directly returns children', () => {
        const children = (

Children

        );
        const wrapper = shallow(
          <CustomProperties
            properties={{ '--foo': 'bar', '--baz': 'bat' }}
            global
          >
            {children}
          </CustomProperties>
        );
        expect(wrapper.equals(children)).to.equal(true);
      })

      it('doesn\'t render any extra elements', () => {
        const wrapper = shallow(
          <CustomProperties
            properties={{ '--foo': 'bar', '--baz': 'bat' }}
            global
          />
        );

        expect(wrapper.isEmptyRender()).to.equal(true);
      })
    });

    describe('on unmount', () => {
      it('removes all the current style properties from the document root', () => {
        const wrapper = mount(
          <CustomProperties
            properties={{ '--foo': 'bar', '--baz': 'bat' }}
            global
          />
        );

        wrapper.unmount();

        expect(utilities.removeStyleProperty.callCount).to.equal(2);
        expect(utilities.removeStyleProperty.calledWith(FAKE_ROOT_NODE, '--foo')).to.equal(true);
        expect(utilities.removeStyleProperty.calledWith(FAKE_ROOT_NODE, '--baz')).to.equal(true);
      });
    });

    describe('when the properties are updated', () => {
      it('sets only the custom properties that have changed', () => {
        const wrapper = mount(
          <CustomProperties
            properties={{ '--foo': 'bar', '--baz': 'bat' }}
            global
          />
        );

        utilities.setStyleProperty.reset();

        wrapper.setProps({
          properties: {
            '--foo': 'bar',
            '--baz': 'updated',
            '--new': 'value',
          }
        });

        expect(utilities.setStyleProperty.callCount).to.equal(2);
        expect(utilities.setStyleProperty.calledWith(FAKE_ROOT_NODE, '--baz', 'updated')).to.equal(true);
        expect(utilities.setStyleProperty.calledWith(FAKE_ROOT_NODE, '--new', 'value')).to.equal(true);
      });

      it('removes any custom properties that are no longer present', () => {
        const wrapper = mount(
          <CustomProperties
            properties={{ '--foo': 'bar', '--baz': 'bat' }}
            global
          />
        );

        wrapper.setProps({
          properties: {
            '--foo': 'bar',
          }
        });

        expect(utilities.removeStyleProperty.callCount).to.equal(1);
        expect(utilities.removeStyleProperty.calledWith(FAKE_ROOT_NODE, '--baz')).to.equal(true);
      });
    });
  });
});
<|endoftext|>


<commit_before>// prepares payloads for Slack responses

'use strict'

const colors = ['#1c4fff', '#e84778', '#ffc229', '#1ae827', '#5389ff'];

module.exports = {
  posts: posts,
  companies: companies,
  nothingFound: nothingFound,
  inputPrompt: inputPrompt,
  error: error
}

// prepares payload from Posts object
function posts(posts) {
    // console.log("Starting preparation of Posts payload: " + JSON.stringify(posts).substring(0,200));
    var payload = {};
    payload.username = 'Gethuman Bot';
    // should this specifically reference the input?
    payload.text = "Here are some issues potentially matching your input, and links for how to resolve them:";
    payload.icon_emoji = ':tada:';
    payload.attachments = [];

    for (let i = 0; i < posts.length; i++) {
        let name = posts[i].companyName || '';
        let color = colors[i];
        let urlId = posts[i].urlId || '';
        let phone = (posts[i].company) ? posts[i].company.callback.phone : '';
        let title = posts[i].title || '';
        if (title.indexOf(name) < 0) {
            title = name + ": " + title;
        };
        let emailContactMethods = posts[i].company.contactMethods.filter(function ( method ) {
            return method.type === "email";
        });
        let email = (emailContactMethods && emailContactMethods.length) ? emailContactMethods[0].target : '';
        let textField = formatTextField(phone, email);
        let singleAttachment = {
            "fallback": "Solution guide for " + name,
            "title": title,
            "color": color,
            "text": textField,
            "fields": [
                {
                    "value": "------------------------------------------------------",
                    "short": false
                },
                {
                    "value": "<https://answers.gethuman.co/_" + encodeURIComponent(urlId) + "|Step by Step Guide>",
                    "short": true
                },
                {
                    "value": "<https://gethuman.com?company=" + encodeURIComponent(name) + "|Solve for me - $20>",
                    "short": true
                }
            ]
        };
        payload.attachments.push(singleAttachment);
    };
    // console.log("Posts payload packaged to send: " + JSON.stringify(payload));
    return payload;
};

// prepares payload from Posts object
function companies(companies) {
    console.log("Starting preparation of Companies payload: " + JSON.stringify(companies).substring(0,200));
    var payload = {};
    payload.username = 'Gethuman Bot';
    payload.text = "We could not find any specific questions matching your input, but here is the contact information for some companies that could help you resolve your issue:";
    payload.icon_emoji = ':flashlight:';
    payload.attachments = [];

    for (let i=0; i < companies.length; i++) {
        let name = companies[i].name || '';
        let color = colors[i];
        let phone = companies[i].callback.phone || '';
        // similar to other email harvest, but not the same
        let emailContactMethods = companies[i].contactMethods.filter(function ( method ) {
            return method.type === "email";
        });
        let email = (emailContactMethods && emailContactMethods.length) ? emailContactMethods[0].target : '';
        let textField = formatTextField(phone, email);
        let singleAttachment = {
            "fallback": "Company info for " + name,
            "title": name,
            "color": color,
            "text": textField,
            "fields": [
                {
                    "value": "<https://gethuman.com?company=" + encodeURIComponent(name) + "|Hire GetHuman to Solve - $20>",
                    "short": true
                }
            ]
        };
        payload.attachments.push(singleAttachment);
    };
    console.log("Companies payload packaged to send: " + JSON.stringify(payload));
    return payload;

}

function nothingFound() {
    var payload = {};
    payload.username = 'Gethuman Bot';
    payload.text = "We could not find anything matching your input to our database. Could you try rephrasing your concern, and be sure to spell the company name correctly?";
    payload.icon_emoji = ':question:';
    return payload;
};

function inputPrompt() {
    var payload = {};
    payload.username = 'Gethuman Bot';
    payload.text = "Tell me your customer service issue.";
    payload.icon_emoji = ':ear:';
    return payload;
};

function error(error) {
    var payload = {};
    payload.username = 'Gethuman Bot';
    payload.text = error;
    payload.icon_emoji = ':no_good:';
    return payload;
};

// helper functions

function formatTextField(phone, email) {
    let result = '';
    if (phone && email) {
        result = phone + " | " + email;
    } else if (phone) {
        result = phone;
    } else if (email) {
        result = email;
    };
    return result;
};

// string of regex's to remove HTML tags from string
// not needed if not displaying solutions text
// function stripHtml(string) {
//     return string.replace(/<\s*br\/*>/gi, "\n")
//       .replace(/<\s*a.*href="(.*?)".*>(.*?)<\/a>/gi, " $2 (Link->$1) ")
//       .replace(/<\s*\/*.+?>/ig, "\n")
//       .replace(/ {2,}/gi, " ")
//       .replace(/\n+\s*/gi, "\n\n");
// }

<commit_msg>Extract functions for message formatting<commit_after>// prepares payloads for Slack responses

'use strict'

const colors = ['#1c4fff', '#e84778', '#ffc229', '#1ae827', '#5389ff'];

module.exports = {
  posts: posts,
  companies: companies,
  nothingFound: nothingFound,
  inputPrompt: inputPrompt,
  error: error
}

// prepares payload from Posts object
function posts(posts) {
    var payload = {};
    payload.username = 'Gethuman Bot';
    // should this specifically reference the input?
    payload.text = "Here are some issues potentially matching your input, and links for how to resolve them:";
    payload.icon_emoji = ':tada:';
    payload.attachments = [];

    for (let i = 0; i < posts.length; i++) {
        let name = posts[i].companyName || '';
        let color = colors[i];
        let urlId = posts[i].urlId || '';
        let title = posts[i].title || '';
        if (title.indexOf(name) < 0) {
            title = name + ": " + title;
        };

        let textField = extractTextFieldFromPost(posts[i]);

        // let phone = (posts[i].company) ? posts[i].company.callback.phone : '';
        // let emailContactMethods = posts[i].company.contactMethods.filter(function ( method ) {
        //     return method.type === "email";
        // });
        // let email = (emailContactMethods && emailContactMethods.length) ? emailContactMethods[0].target : '';
        // let textField = formatTextField(phone, email);

        let singleAttachment = {
            "fallback": "Solution guide for " + name,
            "title": title,
            "color": color,
            "text": textField,
            "fields": [
                {
                    "value": "------------------------------------------------------",
                    "short": false
                },
                {
                    "value": "<https://answers.gethuman.co/_" + encodeURIComponent(urlId) + "|Step by Step Guide>",
                    "short": true
                },
                {
                    "value": "<https://gethuman.com?company=" + encodeURIComponent(name) + "|Solve for me - $20>",
                    "short": true
                }
            ]
        };
        payload.attachments.push(singleAttachment);
    };
    // console.log("Posts payload packaged to send: " + JSON.stringify(payload));
    return payload;
};

// prepares payload from Posts object
function companies(companies) {
    var payload = {};
    payload.username = 'Gethuman Bot';
    payload.text = "We could not find any specific questions matching your input, but here is the contact information for some companies that could help you resolve your issue:";
    payload.icon_emoji = ':flashlight:';
    payload.attachments = [];

    for (let i=0; i < companies.length; i++) {
        let name = companies[i].name || '';
        let color = colors[i];

        let textField = extractTextFieldFromCompany(companies[i]);

        // similar to other email harvest, but not the same
        // let phone = companies[i].callback.phone || '';
        // let emailContactMethods = companies[i].contactMethods.filter(function ( method ) {
        //     return method.type === "email";
        // });
        // let email = (emailContactMethods && emailContactMethods.length) ? emailContactMethods[0].target : '';
        // let textField = formatTextField(phone, email);

        let singleAttachment = {
            "fallback": "Company info for " + name,
            "title": name,
            "color": color,
            "text": textField,
            "fields": [
                {
                    "value": "<https://gethuman.com?company=" + encodeURIComponent(name) + "|Hire GetHuman to Solve - $20>",
                    "short": true
                }
            ]
        };
        payload.attachments.push(singleAttachment);
    };
    // console.log("Companies payload packaged to send: " + JSON.stringify(payload));
    return payload;

}

function nothingFound() {
    var payload = {};
    payload.username = 'Gethuman Bot';
    payload.text = "We could not find anything matching your input to our database. Could you try rephrasing your concern, and be sure to spell the company name correctly?";
    payload.icon_emoji = ':question:';
    return payload;
};

function inputPrompt() {
    var payload = {};
    payload.username = 'Gethuman Bot';
    payload.text = "Tell me your customer service issue.";
    payload.icon_emoji = ':ear:';
    return payload;
};

function error(error) {
    var payload = {};
    payload.username = 'Gethuman Bot';
    payload.text = error;
    payload.icon_emoji = ':no_good:';
    return payload;
};

// ---------------- helper functions ----------------

function extractTextFieldFromPost(post) {
    let phone = (post.company) ? post.company.callback.phone : '';
    let emailContactMethods = post.company.contactMethods.filter(function ( method ) {
        return method.type === "email";
    });
    let email = (emailContactMethods && emailContactMethods.length) ? emailContactMethods[0].target : '';
    return formatTextField(phone, email);
}

function extractTextFieldFromCompany(company) {
    let phone = company.callback.phone || '';
    let emailContactMethods = company.contactMethods.filter(function ( method ) {
        return method.type === "email";
    });
    let email = (emailContactMethods && emailContactMethods.length) ? emailContactMethods[0].target : '';
    let textField = formatTextField(phone, email);
}

function formatTextField(phone, email) {
    let result = '';
    if (phone && email) {
        result = phone + " | " + email;
    } else if (phone) {
        result = phone;
    } else if (email) {
        result = email;
    };
    return result;
};

// string of regex's to remove HTML tags from string
// not needed if not displaying solutions text
// function stripHtml(string) {
//     return string.replace(/<\s*br\/*>/gi, "\n")
//       .replace(/<\s*a.*href="(.*?)".*>(.*?)<\/a>/gi, " $2 (Link->$1) ")
//       .replace(/<\s*\/*.+?>/ig, "\n")
//       .replace(/ {2,}/gi, " ")
//       .replace(/\n+\s*/gi, "\n\n");
// }

<|endoftext|>


<commit_before>
var originalSetTimeout = window.setTimeout,
    originalDateValueOf = Date.prototype.valueOf;

var wait = function(callback, maxWaitCount) {
  maxWaitCount = Ember.isNone(maxWaitCount) ? 100 : maxWaitCount;

  originalSetTimeout(function() {
    if (maxWaitCount > 0 && (Ember.run.hasScheduledTimers() || Ember.run.currentRunLoop)) {
      wait(callback, maxWaitCount - 1);

      return;
    }

    callback();
  }, 10);
};

module('Ember.run.later', {
  teardown: function() {
    window.setTimeout = originalSetTimeout;
    Date.prototype.valueOf = originalDateValueOf;
  }
});

asyncTest('should invoke after specified period of time - function only', function() {

  var invoked = false;

  Ember.run(function() {
    Ember.run.later(function() { invoked = true; }, 100);
  });

  wait(function() {
    start();
    equal(invoked, true, 'should have invoked later item');
  });
});

asyncTest('should invoke after specified period of time - target/method', function() {

  var obj = { invoked: false } ;

  Ember.run(function() {
    Ember.run.later(obj, function() { this.invoked = true; }, 100);
  });

  wait(function() {
    start();
    equal(obj.invoked, true, 'should have invoked later item');
  });
});

asyncTest('should invoke after specified period of time - target/method/args', function() {

  var obj = { invoked: 0 } ;

  Ember.run(function() {
    Ember.run.later(obj, function(amt) { this.invoked += amt; }, 10, 100);
  });

  wait(function() {
    start();
    equal(obj.invoked, 10, 'should have invoked later item');
  });
});

asyncTest('should always invoke within a separate runloop', function() {
  var obj = { invoked: 0 }, firstRunLoop, secondRunLoop;

  Ember.run(function() {
    firstRunLoop = Ember.run.currentRunLoop;

    Ember.run.later(obj, function(amt) {
      this.invoked += amt;
      secondRunLoop = Ember.run.currentRunLoop;
    }, 10, 1);

    // Synchronous "sleep". This simulates work being done
    // after run.later was called but before the run loop
    // has flushed. In previous versions, this would have
    // caused the run.later callback to have run from
    // within the run loop flush, since by the time the
    // run loop has to flush, it would have considered
    // the timer already expired.
    var pauseUntil = +new Date() + 100;
    while(+new Date() < pauseUntil) { /* do nothing - sleeping */ }
  });

  ok(firstRunLoop, "first run loop captured");
  ok(!Ember.run.currentRunLoop, "shouldn't be in a run loop after flush");
  equal(obj.invoked, 0, "shouldn't have invoked later item yet");

  wait(function() {
    start();
    equal(obj.invoked, 10, "should have invoked later item");
    ok(secondRunLoop, "second run loop took place");
    ok(secondRunLoop !== firstRunLoop, "two different run loops took place");
  });
});

asyncTest('callback order', function() {
  var array = [];
  function fn(val) { array.push(val); }

  Ember.run(function() {
    Ember.run.later(this, fn, 4, 5);
    Ember.run.later(this, fn, 1, 1);
    Ember.run.later(this, fn, 5, 10);
    Ember.run.later(this, fn, 2, 3);
    Ember.run.later(this, fn, 3, 3);
  });

  deepEqual(array, []);

  wait(function() {
    start();
    deepEqual(array, [1,2,3,4,5], 'callbacks were called in expected order');
  });
});

asyncTest('callbacks coalesce into same run loop if expiring at the same time', function() {
  var array = [];
  function fn(val) { array.push(Ember.run.currentRunLoop); }

  Ember.run(function() {

    // Force +new Date to return the same result while scheduling
    // run.later timers. Otherwise: non-determinism!
    var now = +new Date();
    Date.prototype.valueOf = function() { return now; };

    Ember.run.later(this, fn, 10);
    Ember.run.later(this, fn, 200);
    Ember.run.later(this, fn, 200);

    Date.prototype.valueOf = originalDateValueOf;
  });

  deepEqual(array, []);

  wait(function() {
    start();
    equal(array.length, 3, 'all callbacks called');
    ok(array[0] !== array[1], 'first two callbacks have different run loops');
    ok(array[0], 'first runloop present');
    ok(array[1], 'second runloop present');
    equal(array[1], array[2], 'last two callbacks got the same run loop');
  });
});

asyncTest('inception calls to run.later should run callbacks in separate run loops', function() {

  var runLoop, finished;

  Ember.run(function() {
    runLoop = Ember.run.currentRunLoop;
    ok(runLoop);

    Ember.run.later(function() {
      ok(Ember.run.currentRunLoop && Ember.run.currentRunLoop !== runLoop,
         'first later callback has own run loop');
      runLoop = Ember.run.currentRunLoop;

      Ember.run.later(function() {
        ok(Ember.run.currentRunLoop && Ember.run.currentRunLoop !== runLoop,
           'second later callback has own run loop');
        finished = true;
      }, 40);
    }, 40);
  });

  wait(function() {
    start();
    ok(finished, 'all .later callbacks run');
  });
});

asyncTest('setTimeout should never run with a negative wait', function() {

  // Rationale: The old run loop code was susceptible to an occasional
  // bug where invokeLaterTimers would be scheduled with a setTimeout
  // with a negative wait. Modern browsers normalize this to 0, but
  // older browsers (IE <= 8) break with a negative wait, which
  // happens when an expired timer callback takes a while to run,
  // which is what we simulate here.
  var newSetTimeoutUsed;
  window.setTimeout = function() {
    var wait = arguments[arguments.length - 1];
    newSetTimeoutUsed = true;
    ok(!isNaN(wait) && wait >= 0, 'wait is a non-negative number');
    // In IE8, `setTimeout.apply` is `undefined`.
    var apply = Function.prototype.apply;
    return apply.apply(originalSetTimeout, [this, arguments]);
  };

  var count = 0;
  Ember.run(function() {

    Ember.run.later(function() {
      count++;

      // This will get run first. Waste some time.
      // This is intended to break invokeLaterTimers code by taking a
      // long enough time that other timers should technically expire. It's
      // fine that they're not called in this run loop; just need to
      // make sure that invokeLaterTimers doesn't end up scheduling
      // a negative setTimeout.
      var pauseUntil = +new Date() + 60;
      while(+new Date() < pauseUntil) { /* do nothing - sleeping */ }
    }, 1);

    Ember.run.later(function() {
      equal(count, 1, 'callbacks called in order');
    }, 50);
  });

  wait(function() {
    window.setTimeout = originalSetTimeout;
    start();
    ok(newSetTimeoutUsed, 'stub setTimeout was used');
  });
});
<commit_msg>Comment out unenforceable `run.later` ordering tests<commit_after>
var originalSetTimeout = window.setTimeout,
    originalDateValueOf = Date.prototype.valueOf;

var wait = function(callback, maxWaitCount) {
  maxWaitCount = Ember.isNone(maxWaitCount) ? 100 : maxWaitCount;

  originalSetTimeout(function() {
    if (maxWaitCount > 0 && (Ember.run.hasScheduledTimers() || Ember.run.currentRunLoop)) {
      wait(callback, maxWaitCount - 1);

      return;
    }

    callback();
  }, 10);
};

module('Ember.run.later', {
  teardown: function() {
    window.setTimeout = originalSetTimeout;
    Date.prototype.valueOf = originalDateValueOf;
  }
});

asyncTest('should invoke after specified period of time - function only', function() {

  var invoked = false;

  Ember.run(function() {
    Ember.run.later(function() { invoked = true; }, 100);
  });

  wait(function() {
    start();
    equal(invoked, true, 'should have invoked later item');
  });
});

asyncTest('should invoke after specified period of time - target/method', function() {

  var obj = { invoked: false } ;

  Ember.run(function() {
    Ember.run.later(obj, function() { this.invoked = true; }, 100);
  });

  wait(function() {
    start();
    equal(obj.invoked, true, 'should have invoked later item');
  });
});

asyncTest('should invoke after specified period of time - target/method/args', function() {

  var obj = { invoked: 0 } ;

  Ember.run(function() {
    Ember.run.later(obj, function(amt) { this.invoked += amt; }, 10, 100);
  });

  wait(function() {
    start();
    equal(obj.invoked, 10, 'should have invoked later item');
  });
});

asyncTest('should always invoke within a separate runloop', function() {
  var obj = { invoked: 0 }, firstRunLoop, secondRunLoop;

  Ember.run(function() {
    firstRunLoop = Ember.run.currentRunLoop;

    Ember.run.later(obj, function(amt) {
      this.invoked += amt;
      secondRunLoop = Ember.run.currentRunLoop;
    }, 10, 1);

    // Synchronous "sleep". This simulates work being done
    // after run.later was called but before the run loop
    // has flushed. In previous versions, this would have
    // caused the run.later callback to have run from
    // within the run loop flush, since by the time the
    // run loop has to flush, it would have considered
    // the timer already expired.
    var pauseUntil = +new Date() + 100;
    while(+new Date() < pauseUntil) { /* do nothing - sleeping */ }
  });

  ok(firstRunLoop, "first run loop captured");
  ok(!Ember.run.currentRunLoop, "shouldn't be in a run loop after flush");
  equal(obj.invoked, 0, "shouldn't have invoked later item yet");

  wait(function() {
    start();
    equal(obj.invoked, 10, "should have invoked later item");
    ok(secondRunLoop, "second run loop took place");
    ok(secondRunLoop !== firstRunLoop, "two different run loops took place");
  });
});

// Our current implementation doesn't allow us to correctly enforce this ordering.
// We should probably implement a queue to provide this guarantee.
// See https://github.com/emberjs/ember.js/issues/3526 for more information.

// asyncTest('callback order', function() {
//   var array = [];
//   function fn(val) { array.push(val); }

//   Ember.run(function() {
//     Ember.run.later(this, fn, 4, 5);
//     Ember.run.later(this, fn, 1, 1);
//     Ember.run.later(this, fn, 5, 10);
//     Ember.run.later(this, fn, 2, 3);
//     Ember.run.later(this, fn, 3, 3);
//   });

//   deepEqual(array, []);

//   wait(function() {
//     start();
//     deepEqual(array, [1,2,3,4,5], 'callbacks were called in expected order');
//   });
// });

asyncTest('callbacks coalesce into same run loop if expiring at the same time', function() {
  var array = [];
  function fn(val) { array.push(Ember.run.currentRunLoop); }

  Ember.run(function() {

    // Force +new Date to return the same result while scheduling
    // run.later timers. Otherwise: non-determinism!
    var now = +new Date();
    Date.prototype.valueOf = function() { return now; };

    Ember.run.later(this, fn, 10);
    Ember.run.later(this, fn, 200);
    Ember.run.later(this, fn, 200);

    Date.prototype.valueOf = originalDateValueOf;
  });

  deepEqual(array, []);

  wait(function() {
    start();
    equal(array.length, 3, 'all callbacks called');
    ok(array[0] !== array[1], 'first two callbacks have different run loops');
    ok(array[0], 'first runloop present');
    ok(array[1], 'second runloop present');
    equal(array[1], array[2], 'last two callbacks got the same run loop');
  });
});

asyncTest('inception calls to run.later should run callbacks in separate run loops', function() {

  var runLoop, finished;

  Ember.run(function() {
    runLoop = Ember.run.currentRunLoop;
    ok(runLoop);

    Ember.run.later(function() {
      ok(Ember.run.currentRunLoop && Ember.run.currentRunLoop !== runLoop,
         'first later callback has own run loop');
      runLoop = Ember.run.currentRunLoop;

      Ember.run.later(function() {
        ok(Ember.run.currentRunLoop && Ember.run.currentRunLoop !== runLoop,
           'second later callback has own run loop');
        finished = true;
      }, 40);
    }, 40);
  });

  wait(function() {
    start();
    ok(finished, 'all .later callbacks run');
  });
});

asyncTest('setTimeout should never run with a negative wait', function() {

  // Rationale: The old run loop code was susceptible to an occasional
  // bug where invokeLaterTimers would be scheduled with a setTimeout
  // with a negative wait. Modern browsers normalize this to 0, but
  // older browsers (IE <= 8) break with a negative wait, which
  // happens when an expired timer callback takes a while to run,
  // which is what we simulate here.
  var newSetTimeoutUsed;
  window.setTimeout = function() {
    var wait = arguments[arguments.length - 1];
    newSetTimeoutUsed = true;
    ok(!isNaN(wait) && wait >= 0, 'wait is a non-negative number');
    // In IE8, `setTimeout.apply` is `undefined`.
    var apply = Function.prototype.apply;
    return apply.apply(originalSetTimeout, [this, arguments]);
  };

  var count = 0;
  Ember.run(function() {

    Ember.run.later(function() {
      count++;

      // This will get run first. Waste some time.
      // This is intended to break invokeLaterTimers code by taking a
      // long enough time that other timers should technically expire. It's
      // fine that they're not called in this run loop; just need to
      // make sure that invokeLaterTimers doesn't end up scheduling
      // a negative setTimeout.
      var pauseUntil = +new Date() + 60;
      while(+new Date() < pauseUntil) { /* do nothing - sleeping */ }
    }, 1);

    Ember.run.later(function() {
      equal(count, 1, 'callbacks called in order');
    }, 50);
  });

  wait(function() {
    window.setTimeout = originalSetTimeout;
    start();
    ok(newSetTimeoutUsed, 'stub setTimeout was used');
  });
});
<|endoftext|>