<commit_before>(function(window, angular, undefined) {'use strict';

var urlBase = "/rest-api-root";
var authHeader = 'authorization';

/**
 * @ngdoc overview
 * @name lbServices
 * @module
 * @description
 *
 * The `lbServices` module provides services for interacting with
 * the models exposed by the LoopBack server via the REST API.
 *
 */
var module = angular.module("lbServices", ['ngResource']);

module
  .factory('LoopBackAuth', function() {
    var props = ['accessTokenId', 'currentUserId'];
    var propsPrefix = '$LoopBack$';

    function LoopBackAuth() {
      var self = this;
      props.forEach(function(name) {
        self[name] = load(name);
      });
      this.rememberMe = undefined;
      this.currentUserData = null;
    }

    LoopBackAuth.prototype.save = function() {
      var self = this;
      var storage = this.rememberMe ? localStorage : sessionStorage;
      props.forEach(function(name) {
        save(storage, name, self[name]);
      });
    };

    LoopBackAuth.prototype.setUser = function(accessTokenId, userId, userData) {
      this.accessTokenId = accessTokenId;
      this.currentUserId = userId;
      this.currentUserData = userData;
    }

    LoopBackAuth.prototype.clearUser = function() {
      this.accessTokenId = null;
      this.currentUserId = null;
      this.currentUserData = null;
    }

    LoopBackAuth.prototype.clearStorage = function() {
      props.forEach(function(name) {
        save(sessionStorage, name, null);
        save(localStorage, name, null);
      });
    };

    return new LoopBackAuth();

    // Note: LocalStorage converts the value to string
    // We are using empty string as a marker for null/undefined values.
    function save(storage, name, value) {
      var key = propsPrefix + name;
      if (value == null) value = '';
      storage[key] = value;
    }

    function load(name) {
      var key = propsPrefix + name;
      return localStorage[key] || sessionStorage[key] || null;
    }
  })
  .config(['$httpProvider', function($httpProvider) {
    $httpProvider.interceptors.push('LoopBackAuthRequestInterceptor');
  }])
  .factory('LoopBackAuthRequestInterceptor', [ '$q', 'LoopBackAuth',
    function($q, LoopBackAuth) {
      return {
        'request': function(config) {

          // filter out non urlBase requests
          if (config.url.substr(0, urlBase.length) !== urlBase) {
            return config;
          }

          if (LoopBackAuth.accessTokenId) {
            config.headers[authHeader] = LoopBackAuth.accessTokenId;
          } else if (config.__isGetCurrentUser__) {
            // Return a stub 401 error for User.getCurrent() when
            // there is no user logged in
            var res = {
              body: { error: { status: 401 } },
              status: 401,
              config: config,
              headers: function() { return undefined; }
            };
            return $q.reject(res);
          }
          return config || $q.when(config);
        }
      }
    }])

  /**
   * @ngdoc object
   * @name lbServices.LoopBackResourceProvider
   * @header lbServices.LoopBackResourceProvider
   * @description
   * Use `LoopBackResourceProvider` to change the global configuration
   * settings used by all models. Note that the provider is available
   * to Configuration Blocks only, see
   * {@link https://docs.angularjs.org/guide/module#module-loading-dependencies Module Loading & Dependencies}
   * for more details.
   *
   * ## Example
   *
   * ```js
   * angular.module('app')
   *  .config(function(LoopBackResourceProvider) {
   *     LoopBackResourceProvider.setAuthHeader('X-Access-Token');
   *  });
   * ```
   */
  .provider('LoopBackResource', function LoopBackResourceProvider() {
    /**
     * @ngdoc method
     * @name lbServices.LoopBackResourceProvider#setAuthHeader
     * @methodOf lbServices.LoopBackResourceProvider
     * @param {string} header The header name to use, e.g. `X-Access-Token`
     * @description
     * Configure the REST transport to use a different header for sending
     * the authentication token. It is sent in the `Authorization` header
     * by default.
     */
    this.setAuthHeader = function(header) {
      authHeader = header;
    };

    /**
     * @ngdoc method
     * @name lbServices.LoopBackResourceProvider#setUrlBase
     * @methodOf lbServices.LoopBackResourceProvider
     * @param {string} url The URL to use, e.g. `/api` or `//example.com/api`.
     * @description
     * Change the URL of the REST API server. By default, the URL provided
     * to the code generator (`lb-ng` or `grunt-loopback-sdk-angular`) is used.
     */
    this.setUrlBase = function(url) {
      urlBase = url;
    };

    /**
     * @ngdoc method
     * @name lbServices.LoopBackResourceProvider#getUrlBase
     * @methodOf lbServices.LoopBackResourceProvider
     * @description
     * Get the URL of the REST API server. The URL provided
     * to the code generator (`lb-ng` or `grunt-loopback-sdk-angular`) is used.
     */
    this.getUrlBase = function() {
      return urlBase;
    };

    this.$get = ['$resource', function($resource) {
      return function(url, params, actions) {
        var resource = $resource(url, params, actions);

        // Angular always calls POST on $save()
        // This hack is based on
        // http://kirkbushell.me/angular-js-using-ng-resource-in-a-more-restful-manner/
        resource.prototype.$save = function(success, error) {
          // Fortunately, LoopBack provides a convenient `upsert` method
          // that exactly fits our needs.
          var result = resource.upsert.call(this, {}, this, success, error);
          return result.$promise || result;
        };
        return resource;
      };
    }];
  });

})(window, window.angular);
<commit_msg>update test fixture so tests pass<commit_after>(function(window, angular, undefined) {'use strict';

var urlBase = "/rest-api-root";
var authHeader = 'authorization';

function getHost(url) {
  var m = url.match(/^(?:https?:)?\/\/([^\/]+)/);
  return m ? m[1] : null;
}

var urlBaseHost = getHost(urlBase) || location.host;

/**
 * @ngdoc overview
 * @name lbServices
 * @module
 * @description
 *
 * The `lbServices` module provides services for interacting with
 * the models exposed by the LoopBack server via the REST API.
 *
 */
var module = angular.module("lbServices", ['ngResource']);

module
  .factory('LoopBackAuth', function() {
    var props = ['accessTokenId', 'currentUserId', 'rememberMe'];
    var propsPrefix = '$LoopBack$';

    function LoopBackAuth() {
      var self = this;
      props.forEach(function(name) {
        self[name] = load(name);
      });
      this.currentUserData = null;
    }

    LoopBackAuth.prototype.save = function() {
      var self = this;
      var storage = this.rememberMe ? localStorage : sessionStorage;
      props.forEach(function(name) {
        save(storage, name, self[name]);
      });
    };

    LoopBackAuth.prototype.setUser = function(accessTokenId, userId, userData) {
      this.accessTokenId = accessTokenId;
      this.currentUserId = userId;
      this.currentUserData = userData;
    }

    LoopBackAuth.prototype.clearUser = function() {
      this.accessTokenId = null;
      this.currentUserId = null;
      this.currentUserData = null;
    }

    LoopBackAuth.prototype.clearStorage = function() {
      props.forEach(function(name) {
        save(sessionStorage, name, null);
        save(localStorage, name, null);
      });
    };

    return new LoopBackAuth();

    // Note: LocalStorage converts the value to string
    // We are using empty string as a marker for null/undefined values.
    function save(storage, name, value) {
      var key = propsPrefix + name;
      if (value == null) value = '';
      storage[key] = value;
    }

    function load(name) {
      var key = propsPrefix + name;
      return localStorage[key] || sessionStorage[key] || null;
    }
  })
  .config(['$httpProvider', function($httpProvider) {
    $httpProvider.interceptors.push('LoopBackAuthRequestInterceptor');
  }])
  .factory('LoopBackAuthRequestInterceptor', [ '$q', 'LoopBackAuth',
    function($q, LoopBackAuth) {
      return {
        'request': function(config) {

          // filter out external requests
          var host = getHost(config.url);
          if (host && host !== urlBaseHost) {
            return config;
          }

          if (LoopBackAuth.accessTokenId) {
            config.headers[authHeader] = LoopBackAuth.accessTokenId;
          } else if (config.__isGetCurrentUser__) {
            // Return a stub 401 error for User.getCurrent() when
            // there is no user logged in
            var res = {
              body: { error: { status: 401 } },
              status: 401,
              config: config,
              headers: function() { return undefined; }
            };
            return $q.reject(res);
          }
          return config || $q.when(config);
        }
      }
    }])

  /**
   * @ngdoc object
   * @name lbServices.LoopBackResourceProvider
   * @header lbServices.LoopBackResourceProvider
   * @description
   * Use `LoopBackResourceProvider` to change the global configuration
   * settings used by all models. Note that the provider is available
   * to Configuration Blocks only, see
   * {@link https://docs.angularjs.org/guide/module#module-loading-dependencies Module Loading & Dependencies}
   * for more details.
   *
   * ## Example
   *
   * ```js
   * angular.module('app')
   *  .config(function(LoopBackResourceProvider) {
   *     LoopBackResourceProvider.setAuthHeader('X-Access-Token');
   *  });
   * ```
   */
  .provider('LoopBackResource', function LoopBackResourceProvider() {
    /**
     * @ngdoc method
     * @name lbServices.LoopBackResourceProvider#setAuthHeader
     * @methodOf lbServices.LoopBackResourceProvider
     * @param {string} header The header name to use, e.g. `X-Access-Token`
     * @description
     * Configure the REST transport to use a different header for sending
     * the authentication token. It is sent in the `Authorization` header
     * by default.
     */
    this.setAuthHeader = function(header) {
      authHeader = header;
    };

    /**
     * @ngdoc method
     * @name lbServices.LoopBackResourceProvider#setUrlBase
     * @methodOf lbServices.LoopBackResourceProvider
     * @param {string} url The URL to use, e.g. `/api` or `//example.com/api`.
     * @description
     * Change the URL of the REST API server. By default, the URL provided
     * to the code generator (`lb-ng` or `grunt-loopback-sdk-angular`) is used.
     */
    this.setUrlBase = function(url) {
      urlBase = url;
      urlBaseHost = getHost(urlBase) || location.host;
    };

    /**
     * @ngdoc method
     * @name lbServices.LoopBackResourceProvider#getUrlBase
     * @methodOf lbServices.LoopBackResourceProvider
     * @description
     * Get the URL of the REST API server. The URL provided
     * to the code generator (`lb-ng` or `grunt-loopback-sdk-angular`) is used.
     */
    this.getUrlBase = function() {
      return urlBase;
    };

    this.$get = ['$resource', function($resource) {
      return function(url, params, actions) {
        var resource = $resource(url, params, actions);

        // Angular always calls POST on $save()
        // This hack is based on
        // http://kirkbushell.me/angular-js-using-ng-resource-in-a-more-restful-manner/
        resource.prototype.$save = function(success, error) {
          // Fortunately, LoopBack provides a convenient `upsert` method
          // that exactly fits our needs.
          var result = resource.upsert.call(this, {}, this, success, error);
          return result.$promise || result;
        };
        return resource;
      };
    }];
  });

})(window, window.angular);
<|endoftext|>


<commit_before>(function () {
    var VisualizationProvider = function() {
        this.visualizations = {};
        this.visualizationTypes = {};
        var defaultConfig = {
            defaultOptions: {},
            skipTypes: false,
            editorTemplate: null
        }

        this.registerVisualization = function(config) {
            var visualization = _.extend({}, defaultConfig, config);

            // TODO: this is prone to errors; better refactor.
            if (_.isEmpty(this.visualizations)) {
                this.defaultVisualization = visualization;
            }

            this.visualizations[config.type] = visualization;

            if (!config.skipTypes) {
                this.visualizationTypes[config.name] = config.type;
            };
        };

        this.getSwitchTemplate = function(property) {
            var pattern = /(<[a-zA-Z0-9-]*?)( |>)/

            var mergedTemplates = _.reduce(this.visualizations, function(templates, visualization) {
                if (visualization[property]) {
                    var ngSwitch = '$1 ng-switch-when="' + visualization.type + '" $2';
                    var template = visualization[property].replace(pattern, ngSwitch);

                    return templates + "\n" + template;
                }

                return templates;
            }, "");

            mergedTemplates = '
'+ mergedTemplates + "
";

            return mergedTemplates;
        }

        this.$get = ['$resource', function($resource) {
            var Visualization = $resource('/api/visualizations/:id', {id: '@id'});
            Visualization.visualizations = this.visualizations;
            Visualization.visualizationTypes = this.visualizationTypes;
            Visualization.renderVisualizationsTemplate = this.getSwitchTemplate('renderTemplate');
            Visualization.editorTemplate = this.getSwitchTemplate('editorTemplate');
            Visualization.defaultVisualization = this.defaultVisualization;

            return Visualization;
        }];
    };

    var VisualizationRenderer = function(Visualization) {
        return {
            restrict: 'E',
            scope: {
                visualization: '=',
                queryResult: '='
            },
            // TODO: using switch here (and in the options editor) might introduce errors and bad
            // performance wise. It's better to eventually show the correct template based on the
            // visualization type and not make the browser render all of them.
            template: Visualization.renderVisualizationsTemplate,
            replace: false
        }
    };

    var VisualizationOptionsEditor = function(Visualization) {
        return {
            restrict: 'E',
            template: Visualization.editorTemplate,
            replace: false
        }
    };

    var EditVisualizationForm = function(Visualization, growl) {
        return {
            restrict: 'E',
            templateUrl: '/views/visualizations/edit_visualization.html',
            replace: true,
            scope: {
                query: '=',
                queryResult: '=',
                visualization: '=?'
            },
            link: function (scope, element, attrs) {
                scope.visTypes = Visualization.visualizationTypes;

                scope.newVisualization = function(q) {
                    return {
                        'query_id': q.id,
                        'type': Visualization.defaultVisualization.type,
                        'name': Visualization.defaultVisualization.name,
                        'description': q.description || '',
                        'options': Visualization.defaultVisualization.defaultOptions
                    };
                }

                if (!scope.visualization) {
                    // create new visualization
                    // wait for query to load to populate with defaults
                    var unwatch = scope.$watch('query', function (q) {
                        if (q && q.id) {
                            unwatch();

                            scope.visualization = scope.newVisualization(q);
                        }
                    }, true);
                }

                scope.$watch('visualization.type', function (type) {
                    // if not edited by user, set name to match type
                    if (type && scope.visualization && !scope.visForm.name.$dirty) {
                        // poor man's titlecase
                        scope.visualization.name = scope.visualization.type[0] + scope.visualization.type.slice(1).toLowerCase();
                    }
                });

                scope.submit = function () {
                    Visualization.save(scope.visualization, function success(result) {
                        growl.addSuccessMessage("Visualization saved");

                        scope.visualization = scope.newVisualization(scope.query);

                        var visIds = _.pluck(scope.query.visualizations, 'id');
                        var index = visIds.indexOf(result.id);
                        if (index > -1) {
                            scope.query.visualizations[index] = result;
                        } else {
                            scope.query.visualizations.push(result);
                        }
                    }, function error() {
                        growl.addErrorMessage("Visualization could not be saved");
                    });
                };
            }
        }
    };

    angular.module('redash.visualization', [])
        .provider('Visualization', VisualizationProvider)
        .directive('visualizationRenderer', ['Visualization', VisualizationRenderer])
        .directive('visualizationOptionsEditor', ['Visualization', VisualizationOptionsEditor])
        .directive('editVisulatizationForm', ['Visualization', 'growl', EditVisualizationForm])
})();
<commit_msg>Fix: custom visualization name was ignored (ref #127)<commit_after>(function () {
    var VisualizationProvider = function() {
        this.visualizations = {};
        this.visualizationTypes = {};
        var defaultConfig = {
            defaultOptions: {},
            skipTypes: false,
            editorTemplate: null
        }

        this.registerVisualization = function(config) {
            var visualization = _.extend({}, defaultConfig, config);

            // TODO: this is prone to errors; better refactor.
            if (_.isEmpty(this.visualizations)) {
                this.defaultVisualization = visualization;
            }

            this.visualizations[config.type] = visualization;

            if (!config.skipTypes) {
                this.visualizationTypes[config.name] = config.type;
            };
        };

        this.getSwitchTemplate = function(property) {
            var pattern = /(<[a-zA-Z0-9-]*?)( |>)/

            var mergedTemplates = _.reduce(this.visualizations, function(templates, visualization) {
                if (visualization[property]) {
                    var ngSwitch = '$1 ng-switch-when="' + visualization.type + '" $2';
                    var template = visualization[property].replace(pattern, ngSwitch);

                    return templates + "\n" + template;
                }

                return templates;
            }, "");

            mergedTemplates = '
'+ mergedTemplates + "
";

            return mergedTemplates;
        }

        this.$get = ['$resource', function($resource) {
            var Visualization = $resource('/api/visualizations/:id', {id: '@id'});
            Visualization.visualizations = this.visualizations;
            Visualization.visualizationTypes = this.visualizationTypes;
            Visualization.renderVisualizationsTemplate = this.getSwitchTemplate('renderTemplate');
            Visualization.editorTemplate = this.getSwitchTemplate('editorTemplate');
            Visualization.defaultVisualization = this.defaultVisualization;

            return Visualization;
        }];
    };

    var VisualizationRenderer = function(Visualization) {
        return {
            restrict: 'E',
            scope: {
                visualization: '=',
                queryResult: '='
            },
            // TODO: using switch here (and in the options editor) might introduce errors and bad
            // performance wise. It's better to eventually show the correct template based on the
            // visualization type and not make the browser render all of them.
            template: Visualization.renderVisualizationsTemplate,
            replace: false
        }
    };

    var VisualizationOptionsEditor = function(Visualization) {
        return {
            restrict: 'E',
            template: Visualization.editorTemplate,
            replace: false
        }
    };

    var EditVisualizationForm = function(Visualization, growl) {
        return {
            restrict: 'E',
            templateUrl: '/views/visualizations/edit_visualization.html',
            replace: true,
            scope: {
                query: '=',
                queryResult: '=',
                visualization: '=?'
            },
            link: function (scope, element, attrs) {
                scope.visTypes = Visualization.visualizationTypes;

                scope.newVisualization = function(q) {
                    return {
                        'query_id': q.id,
                        'type': Visualization.defaultVisualization.type,
                        'name': Visualization.defaultVisualization.name,
                        'description': q.description || '',
                        'options': Visualization.defaultVisualization.defaultOptions
                    };
                }

                if (!scope.visualization) {
                    // create new visualization
                    // wait for query to load to populate with defaults
                    var unwatch = scope.$watch('query', function (q) {
                        if (q && q.id) {
                            unwatch();

                            scope.visualization = scope.newVisualization(q);
                        }
                    }, true);
                }

                scope.$watch('visualization.type', function (type, oldType) {
                    // if not edited by user, set name to match type
                    if (type && oldType != type && scope.visualization && !scope.visForm.name.$dirty) {
                        // poor man's titlecase
                        scope.visualization.name = scope.visualization.type[0] + scope.visualization.type.slice(1).toLowerCase();
                    }
                });

                scope.submit = function () {
                    Visualization.save(scope.visualization, function success(result) {
                        growl.addSuccessMessage("Visualization saved");

                        scope.visualization = scope.newVisualization(scope.query);

                        var visIds = _.pluck(scope.query.visualizations, 'id');
                        var index = visIds.indexOf(result.id);
                        if (index > -1) {
                            scope.query.visualizations[index] = result;
                        } else {
                            scope.query.visualizations.push(result);
                        }
                    }, function error() {
                        growl.addErrorMessage("Visualization could not be saved");
                    });
                };
            }
        }
    };

    angular.module('redash.visualization', [])
        .provider('Visualization', VisualizationProvider)
        .directive('visualizationRenderer', ['Visualization', VisualizationRenderer])
        .directive('visualizationOptionsEditor', ['Visualization', VisualizationOptionsEditor])
        .directive('editVisulatizationForm', ['Visualization', 'growl', EditVisualizationForm])
})();
<|endoftext|>


<commit_before>Feature('Login');

Scenario('Basic auth error', (I, loginPage) => {
  I.amOnPage('/login');
  loginPage.basicLogin('admin', 'invalid');
  I.waitForVisible('[data-error]');
  I.seeElement('[data-error]');
});

Scenario('Basic auth success', (I, loginPage) => {
  I.amOnPage('/login');
  loginPage.basicLogin('admin', 'admin');
  I.waitForVisible('[data-search]', 5);
  I.see('vibioh/dashboard');
});
<commit_msg>Adding delay for async responsve<commit_after>Feature('Login');

Scenario('Basic auth error', (I, loginPage) => {
  I.amOnPage('/login');
  loginPage.basicLogin('admin', 'invalid');
  I.waitForVisible('[data-error]', 5);
  I.seeElement('[data-error]');
});

Scenario('Basic auth success', (I, loginPage) => {
  I.amOnPage('/login');
  loginPage.basicLogin('admin', 'admin');
  I.waitForVisible('[data-search]', 5);
  I.see('vibioh/dashboard');
});
<|endoftext|>


<commit_before>'use strict';

const tileTypes = require('./tileTypes');
const {getVector, normalize} = require('./vectors');

const COLOURS = require('./colours');

const workerStatuses = require('./workerStatuses');

const range = (numberOfElements) => {
    return Array.apply(null, Array(numberOfElements)).map((_, i) => i);
};

const average = elements => {
    let sum = 0;
    elements.forEach(el => {
        sum += el;
    });

    return Math.floor(sum / elements.length + 1);
};

const getRandomInt = (min, max) => (Math.floor(Math.random() * (max - min)) + min);

const theGameFactory = (gridder, logger, stateUpdater, debugState) => {
    const getInitialState = () => {
        return {
            side: 0,
            wheelBarrowPrice: 0,
            goodPrognosis: 0,
            turnTime: 0,
            commandLimit: 0,
            workersCount: 0,
            stack: null,
            stackBorderCoordinates: null,
            scoutId: '',
            floodStatus: {},
            forecast: {},
            workers: [],
            magazines: [],
            objects: [],
            map: [],
        };
    };

    let state = null;

    const getLandTile = (x, y) => {
        return {
            x: x,
            y: y,
            tileType: tileTypes.land
        };
    };

    const updateTile = tile => {
        tile.color = COLOURS[tile.tileType];
        state.map[tile.y][tile.x] = tile;

        if (!state.workers.find(worker => worker.x === tile.x && worker.y === tile.y)) {
            gridder.updateCell(tile);
        }
    };

    const updateStateLog = () => {
        stateUpdater.newState(Object.assign({}, state, {
            workersCount: state.workers.length,
            map: []
        }));
    };

    const markWorkerOnMap = worker => {
        gridder.updateCell(worker);
        state.workers.push(worker);
    };

    const removeWorkerFromMap = workerInState => {
        const workerIndex = state.workers.indexOf(workerInState);
        if (workerIndex === -1) {
            console.log('trying to remove worker not on the map', workerInState);
            return;
        }

        gridder.updateCell(state.map[workerInState.y][workerInState.x]);
        state.workers = [...state.workers.slice(0, workerIndex), ...state.workers.slice(workerIndex + 1)];
    };

    const closestObject = (pointFrom, objects) => {
        let minDistance = 99999;
        let closest = null;

        objects.forEach(object => {
            const vector = getVector(pointFrom, object.coordinates);

            const distance = Math.abs(vector.x) + Math.abs(vector.y);
            if (distance < minDistance) {
                minDistance = distance;
                closest = object;
            }
        });

        return closest;
    };

    const isObject = (x, y) => {
        const type = (state.map[y][x] && state.map[y][x].tileType) || tileTypes.magazine;
        const objectTypes = [tileTypes.myObject, tileTypes.object, tileTypes.magazine, tileTypes.dudeWithSandBags];

        const result = objectTypes.indexOf(type);
        //console.log('is object?!', type, result);

        return result !== -1;
    };

    const getVectorAroundObjects = (pointFrom, pointTo) => {
        const vectors = [
            {x: 0, y: 1},
            {x: 0, y: -1},
            {x: 1, y: 0},
            {x: 1, y: 1},
            {x: 1, y: -1},
            {x: -1, y: 0},
            {x: -1, y: 1},
            {x: -1, y: -1}
        ];

        const withDistances = vectors.map(v => {
            const nextPointFrom = {x: pointFrom.x + v.x, y: pointFrom.y + v.y};

            if (isObject(nextPointFrom.x, nextPointFrom.y)) {
                return {distance: 9999999};
            }

            const possibleVector = getVector(nextPointFrom, pointTo);
            const possibleDistance = Math.abs(possibleVector.x) + Math.abs(possibleVector.y);

            return {
                normalized: v,
                distance: possibleDistance
            };
        });

        let distance = 9999;
        let normalized = null;
        withDistances.forEach(v => {
            if (v.distance < distance) {
                distance = v.distance;
                normalized = v.normalized;
            }
        });

        return normalized;
    };

    const getObjectCoordinates = object => {
        const origin = object.coordinates;

        const maxY = object.size.height;
        const maxX = object.size.width;

        const coordinates = [];

        for (let y = 0; y < maxY; y++) {
            for (let x = 0; x < maxX; x++) {
                coordinates.push({
                    x: origin.x + x,
                    y: origin.y + y
                });
            }
        }

        return coordinates;
    };

    const borderWithNotEnoughBags = stackBorder => {
        const sandBags = state.map[stackBorder.y][stackBorder.x].sandBags;

        const fenceHeight = average(state.forecast.map(f => f.hMax));

        return sandBags < fenceHeight;
    };

    let lastWorldDescriptor = '';
    return {
        init (worldDescriptorResponse) {
            const [side, wheelBarrowPrice, goodPrognosis, turnTime, commandLimit] = worldDescriptorResponse.split(' ');

            const worldDescriptor = `${side} ${wheelBarrowPrice} ${goodPrognosis} ${turnTime} ${commandLimit}`;

            if (worldDescriptor === lastWorldDescriptor) {
                return;
            }

            lastWorldDescriptor = worldDescriptor;

            state = Object.assign({}, getInitialState(), {
                side: parseInt(side, 10),
                wheelBarrowPrice: parseInt(wheelBarrowPrice, 10),
                goodPrognosis: parseInt(goodPrognosis, 10),
                turnTime: parseInt(turnTime, 10),
                commandLimit: parseInt(commandLimit, 10)
            });

            console.log('initing', side, state.side);

            state.map = range(state.side).map(y => range(state.side).map(x => {
                return getLandTile(x, y);
            }));

            gridder.newGrid({
                map: state.map,
                styles: {
                    side: 8,
                    background: COLOURS[tileTypes.land]
                }
            });

            updateStateLog();
        },
        mapObjects (objectsResponse) {
            const objects = objectsResponse.map(objectResponse => {
                const [xCoordinate, yCoordinate, width, height, value, bags] = objectResponse.split(' ');

                return {
                    magazine: bags.toLowerCase() !== 'na',
                    coordinates: {
                        x: parseInt(xCoordinate, 10),
                        y: parseInt(yCoordinate, 10)
                    },
                    size: {
                        width: parseInt(width, 10),
                        height: parseInt(height, 10)
                    },
                    value: parseInt(value, 10),
                    bags: parseInt(bags, 10)
                };
            });

            state.magazines = objects.filter(object => object.magazine);
            state.objects = objects.filter(object => !object.magazine);

            objects.forEach(object => {
                const coordinates = getObjectCoordinates(object);
                coordinates.forEach(objectCoordinate => {
                    const type = object.magazine ? tileTypes.magazine : tileTypes.myObject;

                    updateTile({
                        x: objectCoordinate.x,
                        y: objectCoordinate.y,
                        value: object.value,
                        bags: object.bags,
                        fullObject: object,
                        tileType: type
                    });
                });
            });

            if (!state.stackBorderCoordinates) {
                const stack = closestObject(state.magazines[0].coordinates, state.objects);
                const stackBorderCoordinates = [];
                const maxY = stack.size.height + 2;
                const maxX = stack.size.width + 2;
                for (let y = 0; y < maxY; y++) {
                    for (let x = 0; x < maxX; x++) {
                        const edge =
                            ((y === 0) && (x === 0)) ||
                            ((y === 0) && (x === (maxX - 1))) ||
                            ((y === (maxY - 1)) && (x === 0)) ||
                            ((y === (maxY - 1)) && (x === (maxX - 1)));

                        if (!edge && !isObject(stack.coordinates.x - 1 + x, stack.coordinates.y - 1 + y)) {
                            stackBorderCoordinates.push({
                                x: stack.coordinates.x - 1 + x,
                                y: stack.coordinates.y - 1 + y
                            });
                        }
                    }
                }

                state.stack = stack;
                state.stackBorderCoordinates = stackBorderCoordinates;
            }

            updateStateLog();
        },
        mapWorkers (workersResponse) {
            const workers = workersResponse.map(workerResponse => {
                const [id, x, y, moving, capacity, status] = workerResponse.split(' ');

                const worker = {
                    id: parseInt(id, 10),
                    x: parseInt(x, 10),
                    y: parseInt(y, 10),
                    moving: moving.toLowerCase() === 'y',
                    capacity: parseInt(capacity, 10),
                    status: status.toLowerCase() === 'd' || status.toLowerCase() === 'p' ? workerStatuses[status] : workerStatuses.withBags,
                    bags: parseInt(status, 10),
                    color: COLOURS[tileTypes.worker]
                };

                return worker;
            });

            workers.forEach(worker => {
                const workerInState = state.workers.find(w => w.id === worker.id);

                if (!workerInState) {
                    markWorkerOnMap(worker);

                    return;
                }

                const workerMoved = worker.x !== workerInState.x || worker.y !== workerInState.y;

                if (workerMoved) {
                    removeWorkerFromMap(workerInState);
                    markWorkerOnMap(worker);
                }
            });

            const deadWorkers = state.workers.filter(workerInState => !workers.find(w => w.id === workerInState.id));
            deadWorkers.forEach(deadWorker => removeWorkerFromMap(deadWorker));

            updateStateLog();
        },
        getWorkers () {
            return [...state.workers];
        },
        getTile ({x, y}) {
            return state.map[y][x];
        },
        vectorToStack (pointFrom) {
            const nextBorderWithoutSandbags = state.stackBorderCoordinates.find(stackBorder => borderWithNotEnoughBags(stackBorder)) || state.stackBorderCoordinates[0];

            if (!nextBorderWithoutSandbags) {
                console.log('whole border filled?', state.stackBorderCoordinates);

                return {x: 0, y: 0};
            }

            console.log('GOING TO ==> ', nextBorderWithoutSandbags.x, nextBorderWithoutSandbags.y);

            return getVectorAroundObjects(pointFrom, nextBorderWithoutSandbags);
        },
        vectorToMagazine (pointFrom) {
            return normalize(getVector(pointFrom, state.magazines[0].coordinates));
        },
        isStack ({x, y}) {
            const result = state.stackBorderCoordinates.find(borderCoordinates =>
                borderCoordinates.x === x &&
                borderCoordinates.y === y &&
                borderWithNotEnoughBags(borderCoordinates));
            logger.debug({description: 'is stack?', data: 'IS STACK', tile: state.map[y][x], result});

            return result;
        },
        chartScoutData (scout, scoutResponse) {
            const sandBagsToInt = (sandbagString, tile) => {
                const showResult = result => {
                    // console.log(`${sandbagString} => ${result}`);
                };

                if (sandbagString.toLowerCase() === 'z') {
                    const more = (state.map[tile.y][tile.x].sandBags || 30) + 1;

                    showResult(more);

                    return more;
                }

                const parsed = parseInt(sandbagString, 10);
                if (!isNaN(parsed)) {
                    showResult(parsed);

                    return parsed;
                }

                const sigh = {'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15, 'g': 16, 'h': 17, 'i': 18, 'j': 19, 'k': 20, 'l': 21, 'm': 22, 'n': 23, 'o': 24, 'p': 25, 'q': 26, 'r': 27, 's': 28, 't': 29, 'u': 30};

                showResult(sigh[sandbagString]);

                return sigh[sandbagString];
            };

            for (let y = 1; y < 8; ++y) {
                const yLine = scoutResponse[y - 1];
                for (let x = 1; x < 8; ++x) {
                    const tileType = yLine[x - 1];
                    const tile = {
                        x: scout.x + x - 4,
                        y: scout.y + y - 4
                    };

                    switch (tileType) {
                    case '.':
                        break;
                    case '#':
                        break;
                    case 'W':
                        tile.tileType = tileTypes.magazine;
                        updateTile(tile);
                        break;
                    case 'X':
                        tile.tileType = state.map[tile.y][tile.x].tileType === tileTypes.myObject
                            ? tileTypes.myObject
                            : tileTypes.object;
                        updateTile(tile);
                        break;
                    default:
                        tile.tileType = tileTypes.sandBags;
                        tile.sandBags = sandBagsToInt(tileType.toLowerCase(), tile);
                        updateTile(tile);
                        break;
                    }
                }
            }

            for (let y = 8; y < 15; ++y) {
                const yLine = scoutResponse[y - 1];
                for (let x = 1; x < 8; ++x) {
                    const tileType = yLine[x - 1];
                    const tile = {
                        x: scout.x + x - 4,
                        y: scout.y + y - 4
                    };

                    switch (tileType) {
                    case '.':
                        break;
                    case '#':
                        break;
                    case 'b':
                        break;
                    case 'B':
                        tile.tileType = tileTypes.dudeWithSandBags;
                        updateTile(tile);
                        break;
                    default:
                        tile.tileType = tileTypes.sandBags;
                        tile.sandBags = sandBagsToInt(tileType.toLowerCase(), tile);
                        updateTile(tile);
                        break;
                    }
                }
            }
        },
        floodStatus (response) {
            const [height, tillEnd] = response;

            state.floodStatus = {
                height: parseInt(height, 10),
                tillEnd: parseInt(tillEnd, 10)
            };

            updateStateLog();
        },
        setForecast (response) {
            state.forecast = response.map(singleForecast => {
                const [age, pMin, pMax, hMin, hMax] = singleForecast.split(' ');

                return {
                    age: parseInt(age, 10),
                    pMin: parseInt(pMin, 10),
                    pMax: parseInt(pMax, 10),
                    hMin: parseInt(hMin, 10),
                    hMax: parseInt(hMax, 10)
                };
            });

            updateStateLog();
        },
        isFlooding () {
            return state.floodStatus.height;
        },
        vectorToClosestObject (pointFrom) {
            const startingPoint = pointFrom || state.magazines[0].coordinates;
            return normalize(getVector(startingPoint, closestObject(startingPoint, state.objects).coordinates));
        }
    };
};

module.exports = theGameFactory;
<commit_msg>fixed charting scout data - Ys where too far to the bottom<commit_after>'use strict';

const tileTypes = require('./tileTypes');
const {getVector, normalize} = require('./vectors');

const COLOURS = require('./colours');

const workerStatuses = require('./workerStatuses');

const range = (numberOfElements) => {
    return Array.apply(null, Array(numberOfElements)).map((_, i) => i);
};

const average = elements => {
    let sum = 0;
    elements.forEach(el => {
        sum += el;
    });

    return Math.floor(sum / elements.length + 1);
};

const getRandomInt = (min, max) => (Math.floor(Math.random() * (max - min)) + min);

const theGameFactory = (gridder, logger, stateUpdater, debugState) => {
    const getInitialState = () => {
        return {
            side: 0,
            wheelBarrowPrice: 0,
            goodPrognosis: 0,
            turnTime: 0,
            commandLimit: 0,
            workersCount: 0,
            stack: null,
            stackBorderCoordinates: null,
            scoutId: '',
            floodStatus: {},
            forecast: {},
            workers: [],
            magazines: [],
            objects: [],
            map: [],
        };
    };

    let state = null;

    const getLandTile = (x, y) => {
        return {
            x: x,
            y: y,
            tileType: tileTypes.land
        };
    };

    const updateTile = tile => {
        tile.color = COLOURS[tile.tileType];
        state.map[tile.y][tile.x] = tile;

        if (!state.workers.find(worker => worker.x === tile.x && worker.y === tile.y)) {
            gridder.updateCell(tile);
        }
    };

    const updateStateLog = () => {
        stateUpdater.newState(Object.assign({}, state, {
            workersCount: state.workers.length,
            map: []
        }));
    };

    const markWorkerOnMap = worker => {
        gridder.updateCell(worker);
        state.workers.push(worker);
    };

    const removeWorkerFromMap = workerInState => {
        const workerIndex = state.workers.indexOf(workerInState);
        if (workerIndex === -1) {
            console.log('trying to remove worker not on the map', workerInState);
            return;
        }

        gridder.updateCell(state.map[workerInState.y][workerInState.x]);
        state.workers = [...state.workers.slice(0, workerIndex), ...state.workers.slice(workerIndex + 1)];
    };

    const closestObject = (pointFrom, objects) => {
        let minDistance = 99999;
        let closest = null;

        objects.forEach(object => {
            const vector = getVector(pointFrom, object.coordinates);

            const distance = Math.abs(vector.x) + Math.abs(vector.y);
            if (distance < minDistance) {
                minDistance = distance;
                closest = object;
            }
        });

        return closest;
    };

    const isObject = (x, y) => {
        const type = (state.map[y][x] && state.map[y][x].tileType) || tileTypes.magazine;
        const objectTypes = [tileTypes.myObject, tileTypes.object, tileTypes.magazine, tileTypes.dudeWithSandBags];

        const result = objectTypes.indexOf(type);
        //console.log('is object?!', type, result);

        return result !== -1;
    };

    const getVectorAroundObjects = (pointFrom, pointTo) => {
        const vectors = [
            {x: 0, y: 1},
            {x: 0, y: -1},
            {x: 1, y: 0},
            {x: 1, y: 1},
            {x: 1, y: -1},
            {x: -1, y: 0},
            {x: -1, y: 1},
            {x: -1, y: -1}
        ];

        const withDistances = vectors.map(v => {
            const nextPointFrom = {x: pointFrom.x + v.x, y: pointFrom.y + v.y};

            if (isObject(nextPointFrom.x, nextPointFrom.y)) {
                return {distance: 9999999};
            }

            const possibleVector = getVector(nextPointFrom, pointTo);
            const possibleDistance = Math.abs(possibleVector.x) + Math.abs(possibleVector.y);

            return {
                normalized: v,
                distance: possibleDistance
            };
        });

        let distance = 9999;
        let normalized = null;
        withDistances.forEach(v => {
            if (v.distance < distance) {
                distance = v.distance;
                normalized = v.normalized;
            }
        });

        return normalized;
    };

    const getObjectCoordinates = object => {
        const origin = object.coordinates;

        const maxY = object.size.height;
        const maxX = object.size.width;

        const coordinates = [];

        for (let y = 0; y < maxY; y++) {
            for (let x = 0; x < maxX; x++) {
                coordinates.push({
                    x: origin.x + x,
                    y: origin.y + y
                });
            }
        }

        return coordinates;
    };

    const borderWithNotEnoughBags = stackBorder => {
        const sandBags = state.map[stackBorder.y][stackBorder.x].sandBags;

        const fenceHeight = average(state.forecast.map(f => f.hMax));

        return sandBags < fenceHeight;
    };

    let lastWorldDescriptor = '';
    return {
        init (worldDescriptorResponse) {
            const [side, wheelBarrowPrice, goodPrognosis, turnTime, commandLimit] = worldDescriptorResponse.split(' ');

            const worldDescriptor = `${side} ${wheelBarrowPrice} ${goodPrognosis} ${turnTime} ${commandLimit}`;

            if (worldDescriptor === lastWorldDescriptor) {
                return;
            }

            lastWorldDescriptor = worldDescriptor;

            state = Object.assign({}, getInitialState(), {
                side: parseInt(side, 10),
                wheelBarrowPrice: parseInt(wheelBarrowPrice, 10),
                goodPrognosis: parseInt(goodPrognosis, 10),
                turnTime: parseInt(turnTime, 10),
                commandLimit: parseInt(commandLimit, 10)
            });

            console.log('initing', side, state.side);

            state.map = range(state.side).map(y => range(state.side).map(x => {
                return getLandTile(x, y);
            }));

            gridder.newGrid({
                map: state.map,
                styles: {
                    side: 8,
                    background: COLOURS[tileTypes.land]
                }
            });

            updateStateLog();
        },
        mapObjects (objectsResponse) {
            const objects = objectsResponse.map(objectResponse => {
                const [xCoordinate, yCoordinate, width, height, value, bags] = objectResponse.split(' ');

                return {
                    magazine: bags.toLowerCase() !== 'na',
                    coordinates: {
                        x: parseInt(xCoordinate, 10),
                        y: parseInt(yCoordinate, 10)
                    },
                    size: {
                        width: parseInt(width, 10),
                        height: parseInt(height, 10)
                    },
                    value: parseInt(value, 10),
                    bags: parseInt(bags, 10)
                };
            });

            state.magazines = objects.filter(object => object.magazine);
            state.objects = objects.filter(object => !object.magazine);

            objects.forEach(object => {
                const coordinates = getObjectCoordinates(object);
                coordinates.forEach(objectCoordinate => {
                    const type = object.magazine ? tileTypes.magazine : tileTypes.myObject;

                    updateTile({
                        x: objectCoordinate.x,
                        y: objectCoordinate.y,
                        value: object.value,
                        bags: object.bags,
                        fullObject: object,
                        tileType: type
                    });
                });
            });

            if (!state.stackBorderCoordinates) {
                const stack = closestObject(state.magazines[0].coordinates, state.objects);
                const stackBorderCoordinates = [];
                const maxY = stack.size.height + 2;
                const maxX = stack.size.width + 2;
                for (let y = 0; y < maxY; y++) {
                    for (let x = 0; x < maxX; x++) {
                        const edge =
                            ((y === 0) && (x === 0)) ||
                            ((y === 0) && (x === (maxX - 1))) ||
                            ((y === (maxY - 1)) && (x === 0)) ||
                            ((y === (maxY - 1)) && (x === (maxX - 1)));

                        if (!edge && !isObject(stack.coordinates.x - 1 + x, stack.coordinates.y - 1 + y)) {
                            stackBorderCoordinates.push({
                                x: stack.coordinates.x - 1 + x,
                                y: stack.coordinates.y - 1 + y
                            });
                        }
                    }
                }

                state.stack = stack;
                state.stackBorderCoordinates = stackBorderCoordinates;
            }

            updateStateLog();
        },
        mapWorkers (workersResponse) {
            const workers = workersResponse.map(workerResponse => {
                const [id, x, y, moving, capacity, status] = workerResponse.split(' ');

                const worker = {
                    id: parseInt(id, 10),
                    x: parseInt(x, 10),
                    y: parseInt(y, 10),
                    moving: moving.toLowerCase() === 'y',
                    capacity: parseInt(capacity, 10),
                    status: status.toLowerCase() === 'd' || status.toLowerCase() === 'p' ? workerStatuses[status] : workerStatuses.withBags,
                    bags: parseInt(status, 10),
                    color: COLOURS[tileTypes.worker]
                };

                return worker;
            });

            workers.forEach(worker => {
                const workerInState = state.workers.find(w => w.id === worker.id);

                if (!workerInState) {
                    markWorkerOnMap(worker);

                    return;
                }

                const workerMoved = worker.x !== workerInState.x || worker.y !== workerInState.y;

                if (workerMoved) {
                    removeWorkerFromMap(workerInState);
                    markWorkerOnMap(worker);
                }
            });

            const deadWorkers = state.workers.filter(workerInState => !workers.find(w => w.id === workerInState.id));
            deadWorkers.forEach(deadWorker => removeWorkerFromMap(deadWorker));

            updateStateLog();
        },
        getWorkers () {
            return [...state.workers];
        },
        getTile ({x, y}) {
            return state.map[y][x];
        },
        vectorToStack (pointFrom) {
            const nextBorderWithoutSandbags = state.stackBorderCoordinates.find(stackBorder => borderWithNotEnoughBags(stackBorder)) || state.stackBorderCoordinates[0];

            if (!nextBorderWithoutSandbags) {
                console.log('whole border filled?', state.stackBorderCoordinates);

                return {x: 0, y: 0};
            }

            console.log('GOING TO ==> ', nextBorderWithoutSandbags.x, nextBorderWithoutSandbags.y);

            return getVectorAroundObjects(pointFrom, nextBorderWithoutSandbags);
        },
        vectorToMagazine (pointFrom) {
            return normalize(getVector(pointFrom, state.magazines[0].coordinates));
        },
        isStack ({x, y}) {
            const result = state.stackBorderCoordinates.find(borderCoordinates =>
                borderCoordinates.x === x &&
                borderCoordinates.y === y &&
                borderWithNotEnoughBags(borderCoordinates));
            logger.debug({description: 'is stack?', data: 'IS STACK', tile: state.map[y][x], result});

            return result;
        },
        chartScoutData (scout, scoutResponse) {
            const sandBagsToInt = (sandbagString, tile) => {
                const showResult = result => {
                    // console.log(`${sandbagString} => ${result}`);
                };

                if (sandbagString.toLowerCase() === 'z') {
                    const more = (state.map[tile.y][tile.x].sandBags || 30) + 1;

                    showResult(more);

                    return more;
                }

                const parsed = parseInt(sandbagString, 10);
                if (!isNaN(parsed)) {
                    showResult(parsed);

                    return parsed;
                }

                const sigh = {'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15, 'g': 16, 'h': 17, 'i': 18, 'j': 19, 'k': 20, 'l': 21, 'm': 22, 'n': 23, 'o': 24, 'p': 25, 'q': 26, 'r': 27, 's': 28, 't': 29, 'u': 30};

                showResult(sigh[sandbagString]);

                return sigh[sandbagString];
            };

            for (let y = 1; y < 8; ++y) {
                const yLine = scoutResponse[y - 1];
                for (let x = 1; x < 8; ++x) {
                    const tileType = yLine[x - 1];
                    const tile = {
                        x: scout.x + x - 4,
                        y: scout.y + y - 4
                    };

                    switch (tileType) {
                    case '.':
                        break;
                    case '#':
                        break;
                    case 'W':
                        tile.tileType = tileTypes.magazine;
                        updateTile(tile);
                        break;
                    case 'X':
                        tile.tileType = state.map[tile.y][tile.x].tileType === tileTypes.myObject
                            ? tileTypes.myObject
                            : tileTypes.object;
                        updateTile(tile);
                        break;
                    default:
                        tile.tileType = tileTypes.sandBags;
                        tile.sandBags = sandBagsToInt(tileType.toLowerCase(), tile);
                        updateTile(tile);
                        break;
                    }
                }
            }

            for (let y = 8; y < 15; ++y) {
                const yLine = scoutResponse[y - 1];
                for (let x = 1; x < 8; ++x) {
                    const tileType = yLine[x - 1];
                    const tile = {
                        x: scout.x + x - 4,
                        y: scout.y + y - 7 - 4
                    };

                    switch (tileType) {
                    case '.':
                        break;
                    case '#':
                        break;
                    case 'b':
                        break;
                    case 'B':
                        tile.tileType = tileTypes.dudeWithSandBags;
                        updateTile(tile);
                        break;
                    default:
                        tile.tileType = tileTypes.sandBags;
                        tile.sandBags = sandBagsToInt(tileType.toLowerCase(), tile);
                        updateTile(tile);
                        break;
                    }
                }
            }
        },
        floodStatus (response) {
            const [height, tillEnd] = response;

            state.floodStatus = {
                height: parseInt(height, 10),
                tillEnd: parseInt(tillEnd, 10)
            };

            updateStateLog();
        },
        setForecast (response) {
            state.forecast = response.map(singleForecast => {
                const [age, pMin, pMax, hMin, hMax] = singleForecast.split(' ');

                return {
                    age: parseInt(age, 10),
                    pMin: parseInt(pMin, 10),
                    pMax: parseInt(pMax, 10),
                    hMin: parseInt(hMin, 10),
                    hMax: parseInt(hMax, 10)
                };
            });

            updateStateLog();
        },
        isFlooding () {
            return state.floodStatus.height;
        },
        vectorToClosestObject (pointFrom) {
            const startingPoint = pointFrom || state.magazines[0].coordinates;
            return normalize(getVector(startingPoint, closestObject(startingPoint, state.objects).coordinates));
        }
    };
};

module.exports = theGameFactory;
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2016 Samsung Electronics, Inc.,
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
"use strict";

const database = require("../util/database");
const log = require('../util/logging');
const route = require('./route');
const project = require('./project');
const server = require('./server');
const http = require('http');
const fs = require('fs');
const Promise = require('bluebird');
const rp = require('request-promise');
const mysql = require("mysql");
const _ = require("lodash");

exports.getAll = function(req, res) {
    const activeServerList = _.filter(server.getServerListInternal(), {'active': true});
    let allRows = [];
    let promises = [];

    Promise.map(activeServerList, (server) => {
        promises.push(new Promise((resolve) => {
            const userListUrl = `http://${server.hostIP}:${server.portNumber}/api/v2/user-list`;
            rp(userListUrl)
                .then((data) => {
                    const rows = JSON.parse('' + data).rows;
                    if (rows) {
                        rows.forEach((row) => {
                            allRows.push({
                                projectName: server.projectName,
                                userId: row.userId
                            });
                        });
                    }
                    resolve();
                })
                .catch((err) => {
                    log.error(`Failed to get user list for pid ${server.pid} : ${err}`);
                    resolve();
                });
        }));
    });

    Promise.all(promises)
        .then(() => {
            allRows = _.sortBy(allRows, 'projectName');
            res.send({status:'ok', rows: allRows});
        })
        .catch((err) => {
            log.error(err);
            res.send({status:"fail", errorMessage: err.message});
        });
};

function processReturnedData(data) {
    return data.replace(/((\])|(\[))/g,'').replace(/(^\s*)|(\s*$)/g,'');
}

function loadUserInfo(userId, userInfoUrl, userInfoList) {
    return rp(userInfoUrl + userId)
        .then((data) => {
            data = processReturnedData(data);
            if (!data || !validateUserInfoJson(data, userId)) {
                log.error('Not found user data: ' + userId);
                userInfoList.push({'userId':userId});
            } else {
                const infoJson = JSON.parse('' + data);
                userInfoList.push({
                    'userId':userId,
                    'name':infoJson.cn,
                    'department':infoJson.department,
                    'title':infoJson.title,
                    'employeeNumber':infoJson.employeenumber});
            }
        })
        .catch((err) => {
            log.error(err);
            userInfoList.push({'userId':userId});
        });
}

exports.getMoreInfoByUserIdList = function(req, res) {
    const configText = fs.readFileSync("./config.json", 'utf8');
    const configJson = JSON.parse(configText);
    const userInfoUrl = configJson.userInfoUrl;
    const userIdList = req.params.userIdList.split(",");

    let userInfoList = [];
    let promises = [];
    userIdList.forEach((userId) => {
        promises.push(loadUserInfo(userId, userInfoUrl, userInfoList));
    });

    Promise.all(promises)
        .then(() => {
            res.send({status:'ok', rows: userInfoList});
        })
        .catch((err) => {
            log.error(err);
            res.send({status:"fail", errorMessage: err.message});
        });
};

function validateUserInfoJson(data, userid) {
    if(data.indexOf("\"userid\":\"" + userid + "\"") < 0) {
        log.error('Incorrect result from user info server');
        return false;
    }
    return true;
}

exports.getUserCountByProjectName = function(req, res) {
    const projectServer = _.find(server.getServerListInternal(), {'projectName': req.params.projectName});
    const userCountUrl = `http://${projectServer.hostIP}:${projectServer.portNumber}/api/v2/user-count`;
    rp(userCountUrl)
        .then((data) => {
            const parsedData = JSON.parse('' + data);
            res.send({status:'ok', value: parsedData.value});
        })
        .catch((err) => {
            log.error(`Failed to get user count for pid ${projectServer.pid} : ${err}`);
            res.send({status:"fail", errorMessage: err.message});
        });
};

function loadUserStatusList() {
    const sql = "SELECT userId, dexterYn FROM DexterUserList";
    return database.exec(sql)
        .then((rows) => {
            return rows;
        })
        .catch((err) => {
            log.error(err);
            return [];
        });
}

function loadUserList() {
    const activeServerList = _.filter(server.getServerListInternal(), {'active': true});
    let allRows = [];
    let promises = [];

    Promise.map(activeServerList, (server) => {
        promises.push(new Promise((resolve) => {
            const userListUrl = `http://${server.hostIP}:${server.portNumber}/api/v2/user-list`;
            rp(userListUrl)
                .then((data) => {
                    const rows = JSON.parse('' + data).rows;
                    if (rows) {
                        allRows = _.union(allRows, rows);
                    }
                    resolve();
                })
                .catch((err) => {
                    log.error(`Failed to get user list for pid ${server.pid} : ${err}`);
                    resolve();
                });
        }));
    });

    return Promise.all(promises)
        .then(() => {
            allRows = _.uniq(allRows, (row) => {
                return row.userId;
            });
            return allRows;
        })
        .catch((err) => {
            log.error(err);
            return [];
        });
}

function updateUserStatusList() {
    let promises = [];
    let userStatusList = [];
    let userList = [];

    promises.push(new Promise((resolve, reject) => {
        loadUserStatusList()
            .then((rows) => {
                userStatusList = rows;
                resolve();
            })
            .catch((err) => {
                log.error(err);
                reject();
            })
    }));
    promises.push(new Promise((resolve, reject) => {
        loadUserList()
            .then((rows) => {
                userList = rows;
                resolve();
            })
            .catch((err) => {
                log.error(err);
                reject();
            })
    }));

    return Promise.all(promises)
        .then(() => {
            return Promise.map(userStatusList, (row) => {
                if (_.findIndex(userList, (user) => user.userId == row.userId) >= 0) {
                    row.dexterYn = 'Y';
                } else if (row.dexterYn == 'Y') {
                    row.dexterYn = 'N';
                } else {
                    return;
                }

                const sql =
                    `UPDATE DexterUserList
                     SET dexterYn='${row.dexterYn}', reason='', ide='', language=''
                     WHERE userId='${row.userId}'`;
                return database.exec(sql)
                    .catch((err) => {
                        log.error('Failed to set dexterYn : ' + err);
                    });
            });
        });
}

function loadAndCreateUserStatusTable() {
    const sql = `SELECT DexterUserList.userLab AS groupName,
                        COUNT(DexterUserList.userId) AS allDeveloperCount,
                        installedDeveloperCount,
                        nonTargetDeveloperCount
                 FROM DexterUserList
                 LEFT JOIN (SELECT userLab, COUNT(DUL1.userId) AS installedDeveloperCount
                            FROM DexterUserList AS DUL1
                            WHERE DUL1.dexterYn='y'
                            GROUP BY DUL1.userLab) AS d1
                 ON DexterUserList.userLab = d1.userLab
                 LEFT JOIN (SELECT userLab, COUNT(DUL2.userId) AS nonTargetDeveloperCount
                            FROM DexterUserList AS DUL2
                            WHERE DUL2.dexterYn='u'
                            GROUP BY DUL2.userLab) AS d2
                 ON DexterUserList.userLab = d2.userLab
                 GROUP BY DexterUserList.userLab`;
    return database.exec(sql)
        .then((rows) => {
            rows.forEach((row) => {
                if (!row.installedDeveloperCount) {
                    row.installedDeveloperCount = 0;
                }
                row.targetDeveloperCount = getTargetDeveloperCount(row);
                row.installationRatio = getInstallationRatio(row);
            });
            return rows;
        });
}

function getTargetDeveloperCount(row) {
    return row.allDeveloperCount - row.nonTargetDeveloperCount;
}

function getInstallationRatio(row) {
    return (row.installedDeveloperCount / row.targetDeveloperCount * 100).toFixed(1);
}

exports.getUserStatus = function(req, res) {
    updateUserStatusList()
        .then(loadAndCreateUserStatusTable)
        .then((userStatusTable) => {
            res.send({status:'ok', rows: userStatusTable});
        })
        .catch((err) => {
            res.send({status:"fail", errorMessage: err.message});
        });
};

exports.getUserStatusInternal = function() {
    return updateUserStatusList()
        .then(loadAndCreateUserStatusTable)
        .then((userStatusTable) => {
            return userStatusTable;
        })
        .catch((err) => {
            log.error(`Failed to get user status : ${err}`);
            return [];
        });
};<commit_msg>[monitor] Set zero default value to installedDeveloperCount<commit_after>/**
 * Copyright (c) 2016 Samsung Electronics, Inc.,
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
"use strict";

const database = require("../util/database");
const log = require('../util/logging');
const route = require('./route');
const project = require('./project');
const server = require('./server');
const http = require('http');
const fs = require('fs');
const Promise = require('bluebird');
const rp = require('request-promise');
const mysql = require("mysql");
const _ = require("lodash");

exports.getAll = function(req, res) {
    const activeServerList = _.filter(server.getServerListInternal(), {'active': true});
    let allRows = [];
    let promises = [];

    Promise.map(activeServerList, (server) => {
        promises.push(new Promise((resolve) => {
            const userListUrl = `http://${server.hostIP}:${server.portNumber}/api/v2/user-list`;
            rp(userListUrl)
                .then((data) => {
                    const rows = JSON.parse('' + data).rows;
                    if (rows) {
                        rows.forEach((row) => {
                            allRows.push({
                                projectName: server.projectName,
                                userId: row.userId
                            });
                        });
                    }
                    resolve();
                })
                .catch((err) => {
                    log.error(`Failed to get user list for pid ${server.pid} : ${err}`);
                    resolve();
                });
        }));
    });

    Promise.all(promises)
        .then(() => {
            allRows = _.sortBy(allRows, 'projectName');
            res.send({status:'ok', rows: allRows});
        })
        .catch((err) => {
            log.error(err);
            res.send({status:"fail", errorMessage: err.message});
        });
};

function processReturnedData(data) {
    return data.replace(/((\])|(\[))/g,'').replace(/(^\s*)|(\s*$)/g,'');
}

function loadUserInfo(userId, userInfoUrl, userInfoList) {
    return rp(userInfoUrl + userId)
        .then((data) => {
            data = processReturnedData(data);
            if (!data || !validateUserInfoJson(data, userId)) {
                log.error('Not found user data: ' + userId);
                userInfoList.push({'userId':userId});
            } else {
                const infoJson = JSON.parse('' + data);
                userInfoList.push({
                    'userId':userId,
                    'name':infoJson.cn,
                    'department':infoJson.department,
                    'title':infoJson.title,
                    'employeeNumber':infoJson.employeenumber});
            }
        })
        .catch((err) => {
            log.error(err);
            userInfoList.push({'userId':userId});
        });
}

exports.getMoreInfoByUserIdList = function(req, res) {
    const configText = fs.readFileSync("./config.json", 'utf8');
    const configJson = JSON.parse(configText);
    const userInfoUrl = configJson.userInfoUrl;
    const userIdList = req.params.userIdList.split(",");

    let userInfoList = [];
    let promises = [];
    userIdList.forEach((userId) => {
        promises.push(loadUserInfo(userId, userInfoUrl, userInfoList));
    });

    Promise.all(promises)
        .then(() => {
            res.send({status:'ok', rows: userInfoList});
        })
        .catch((err) => {
            log.error(err);
            res.send({status:"fail", errorMessage: err.message});
        });
};

function validateUserInfoJson(data, userid) {
    if(data.indexOf("\"userid\":\"" + userid + "\"") < 0) {
        log.error('Incorrect result from user info server');
        return false;
    }
    return true;
}

exports.getUserCountByProjectName = function(req, res) {
    const projectServer = _.find(server.getServerListInternal(), {'projectName': req.params.projectName});
    const userCountUrl = `http://${projectServer.hostIP}:${projectServer.portNumber}/api/v2/user-count`;
    rp(userCountUrl)
        .then((data) => {
            const parsedData = JSON.parse('' + data);
            res.send({status:'ok', value: parsedData.value});
        })
        .catch((err) => {
            log.error(`Failed to get user count for pid ${projectServer.pid} : ${err}`);
            res.send({status:"fail", errorMessage: err.message});
        });
};

function loadUserStatusList() {
    const sql = "SELECT userId, dexterYn FROM DexterUserList";
    return database.exec(sql)
        .then((rows) => {
            return rows;
        })
        .catch((err) => {
            log.error(err);
            return [];
        });
}

function loadUserList() {
    const activeServerList = _.filter(server.getServerListInternal(), {'active': true});
    let allRows = [];
    let promises = [];

    Promise.map(activeServerList, (server) => {
        promises.push(new Promise((resolve) => {
            const userListUrl = `http://${server.hostIP}:${server.portNumber}/api/v2/user-list`;
            rp(userListUrl)
                .then((data) => {
                    const rows = JSON.parse('' + data).rows;
                    if (rows) {
                        allRows = _.union(allRows, rows);
                    }
                    resolve();
                })
                .catch((err) => {
                    log.error(`Failed to get user list for pid ${server.pid} : ${err}`);
                    resolve();
                });
        }));
    });

    return Promise.all(promises)
        .then(() => {
            allRows = _.uniq(allRows, (row) => {
                return row.userId;
            });
            return allRows;
        })
        .catch((err) => {
            log.error(err);
            return [];
        });
}

function updateUserStatusList() {
    let promises = [];
    let userStatusList = [];
    let userList = [];

    promises.push(new Promise((resolve, reject) => {
        loadUserStatusList()
            .then((rows) => {
                userStatusList = rows;
                resolve();
            })
            .catch((err) => {
                log.error(err);
                reject();
            })
    }));
    promises.push(new Promise((resolve, reject) => {
        loadUserList()
            .then((rows) => {
                userList = rows;
                resolve();
            })
            .catch((err) => {
                log.error(err);
                reject();
            })
    }));

    return Promise.all(promises)
        .then(() => {
            return Promise.map(userStatusList, (row) => {
                if (_.findIndex(userList, (user) => user.userId == row.userId) >= 0) {
                    row.dexterYn = 'Y';
                } else if (row.dexterYn == 'Y') {
                    row.dexterYn = 'N';
                } else {
                    return;
                }

                const sql =
                    `UPDATE DexterUserList
                     SET dexterYn='${row.dexterYn}', reason='', ide='', language=''
                     WHERE userId='${row.userId}'`;
                return database.exec(sql)
                    .catch((err) => {
                        log.error('Failed to set dexterYn : ' + err);
                    });
            });
        });
}

function loadAndCreateUserStatusTable() {
    const sql = `SELECT DexterUserList.userLab AS groupName,
                        IFNULL(COUNT(DexterUserList.userId), 0) AS allDeveloperCount,
                        IFNULL(installedDeveloperCount, 0) AS installedDeveloperCount,
                        IFNULL(nonTargetDeveloperCount, 0) AS nonTargetDeveloperCount
                 FROM DexterUserList
                 LEFT JOIN (SELECT userLab, COUNT(DUL1.userId) AS installedDeveloperCount
                            FROM DexterUserList AS DUL1
                            WHERE DUL1.dexterYn='y'
                            GROUP BY DUL1.userLab) AS d1
                 ON DexterUserList.userLab = d1.userLab
                 LEFT JOIN (SELECT userLab, COUNT(DUL2.userId) AS nonTargetDeveloperCount
                            FROM DexterUserList AS DUL2
                            WHERE DUL2.dexterYn='u'
                            GROUP BY DUL2.userLab) AS d2
                 ON DexterUserList.userLab = d2.userLab
                 GROUP BY DexterUserList.userLab`;
    return database.exec(sql)
        .then((rows) => {
            rows.forEach((row) => {
                row.targetDeveloperCount = getTargetDeveloperCount(row);
                row.installationRatio = getInstallationRatio(row);
            });
            return rows;
        });
}

function getTargetDeveloperCount(row) {
    return row.allDeveloperCount - row.nonTargetDeveloperCount;
}

function getInstallationRatio(row) {
    return (row.installedDeveloperCount / row.targetDeveloperCount * 100).toFixed(1);
}

exports.getUserStatus = function(req, res) {
    updateUserStatusList()
        .then(loadAndCreateUserStatusTable)
        .then((userStatusTable) => {
            res.send({status:'ok', rows: userStatusTable});
        })
        .catch((err) => {
            res.send({status:"fail", errorMessage: err.message});
        });
};

exports.getUserStatusInternal = function() {
    return updateUserStatusList()
        .then(loadAndCreateUserStatusTable)
        .then((userStatusTable) => {
            return userStatusTable;
        })
        .catch((err) => {
            log.error(`Failed to get user status : ${err}`);
            return [];
        });
};<|endoftext|>


<commit_before>/** @flow */
import React from 'react';
import Immutable from 'immutable';
import { isVariationComponent } from '../../components/variation';
import Variation, { isVariationContainer } from '../../containers/variation';
import selectVariation from '../../utils/select-variation';
import { cacheStore } from '../../utils/create-cache-store';
import getKey from '../../utils/get-key';

type Props = {
  /**
   * Redux State:
   * ```js
   *   reduxAbTest = {
   *     experiments: [],
   *     active: {
   *       "experiment.name" => "variation.name",
   *       ...
   *     }
   *     ...
   *   }
   * ```
   */
  reduxAbTest: Immutable.Map,
  /**
   * Selector key for choosing an experiment from the redux store.
   * This can be used as such:
   *  <Experiment selector=`Homepage Header Experiment`> ... </Experiment>
   */
  selector: ?string,
  /**
   * ID of the experiment, this is used to pick a specific experient from the redux store.
   * We use getKey to generate the `key` for this experiment.
   */
  id: ?string,
  /**
   * Name of the experiment, this is used to pick a specific experient from the redux store.
   * This should only be used as a fallback if `id` is unavailable
   */
  name: string,
  /**
   * Name of the default variation.
   * >  When defined, this value is used to choose a variation if a stored value is not present.
   * >  This property may be useful for server side rendering but is otherwise not recommended.
   */
  defaultVariationName: ?string,
  /**
   * Action Creator callback: Function({experiment:ExperimentType})
   */
  dispatchActivate: Function,
  /**
   * Action Creator callback: Function({experiment:ExperimentType})
   */
  dispatchDeactivate: Function,
  /**
   * Action Creator callback: Function({experiment:ExperimentType, variation:VariationType})
   */
  dispatchPlay: Function,
  /**
   * Action Creator callback: Function({experiment:ExperimentType, variation:VariationType})
   */
  dispatchWin: Function,
  /**
   * Variation Children to render
   */
  children: any,
};

type State = {
  /**
   * The currenly active experiment
   */
  experiment: ?ExperimentType,
  /**
   * The currenly active variation
   */
  variation: ?VariationType,
  /**
   * The experient was played
   */
  played: ?bool,
};

const isValid = (child) => (isVariationContainer(child) || isVariationComponent(child));

export default class Experiment extends React.Component {
  props: Props;
  state: State;
  static defaultProps = {
    reduxAbTest:          Immutable.Map({}),
    id:                   null,
    name:                 null,
    selector:             null,
    defaultVariationName: null,
    dispatchActivate:     () => {},
    dispatchDeactivate:   () => {},
    dispatchPlay:         () => {},
    dispatchWin:          () => {},
  };
  state = {
    variation:  null,
    experiment: null,
    played:     false,
  };

  /**
   * Activate the variation
   */
  componentWillMount() {
    console.log('-----');
    console.log(`componentWillMount children.count=${React.Children.count(this.props.children)}`);
    const { id, name, selector, defaultVariationName, reduxAbTest, dispatchActivate, dispatchPlay } = this.props;
    const experiment = this.getExperiment(reduxAbTest, selector, id, name);

    // If the experiment is unavailable, then record it wasn't played and move on
    if (!experiment) {
      this.setState({ experiment: null, variation: null, played: false });
      return;
    }

    const variation = selectVariation({
      active: reduxAbTest.get('active'),
      experiment,
      defaultVariationName,
      cacheStore
    });

    // These will trigger `componentWillReceiveProps`
    dispatchActivate({experiment});
    dispatchPlay({experiment, variation});
    this.setState({ experiment, variation, played: true });
  }

  /**
   * Update the component's state with the new properties
   */
  componentWillReceiveProps(nextProps:Props) {
    console.log(`componentWillReceiveProps children.count=${React.Children.count(this.props.children)}`);
    const { selector, id, name, defaultVariationName, reduxAbTest, dispatchActivate, dispatchPlay } = nextProps;
    const experiment = this.getExperiment(reduxAbTest, selector, id, name);
    if (!experiment) {
      // If we no-longer have an experiment anymore, then update the internal state
      if (this.state.experiment) {
        this.setState({ experiment: null, variation: null, played: false });
      }
      return;
    }

    const variation = selectVariation({
      active: reduxAbTest.get('active'),
      experiment,
      defaultVariationName,
      cacheStore
    });

    if (!experiment.equals(this.state.experiment) || !variation.equals(this.state.variation)) {
      // These will trigger `componentWillReceiveProps`
      if (!this.state.played) {
        dispatchActivate({experiment});
        dispatchPlay({experiment, variation});
      }
      this.setState({ experiment, variation, played: true });
    }
  }

  getExperiment(reduxAbTest, selector, id, name) {
    // Find the key of the currently available experiment
    const key = reduxAbTest.getIn(['availableExperiments', selector || id || name], null);
    if (!key) {
      return null;
    }
    // Select the experiment from the redux store
    const experiment = reduxAbTest.get('experiments').find(experiment => (getKey(experiment) === key), null);
    // Return the resulting experiment
    return experiment;
  }

  /**
   * Deactivate the variation from the state
   */
  componentWillUnmount() {
    const { dispatchDeactivate } = this.props;
    const { experiment } = this.state;
    // Dispatch the deactivation event
    if (experiment) {
      dispatchDeactivate({experiment});
    }
  }

  /**
   * Render one of the variations or `null`
   */
  render() {
    console.log(`render children.count=${React.Children.count(this.props.children)}`);
    const { children } = this.props;
    const { experiment, variation } = this.state;

    const childrenArray = React.Children.toArray(children);

    // If there are no children, render nothing
    if (childrenArray.length === 0) {
      return null;
    }

    // If the first child is text or an unknown component, simply wrap it in a Variation
    if (childrenArray.length === 1 && !isValid(childrenArray[0])) {
      return (
        <Variation id={variation.get('id')} name={variation.get('name')} experiment={experiment} variation={variation}>
          {children}
        </Variation>
      );
    }

    const selectedChild = childrenArray.find( child => (child.props.id === variation.get('id') && variation.get('id')) ) || childrenArray.find( child => (child.props.name === variation.get('name') && variation.get('name')) );
    if (!selectedChild) {
      throw new Error(`Expected to find a Variation child matching id=${variation.get('id')} or name=${variation.get('name')}`);
    }

    //
    // // Hash of "name" => Variation element
    // const variationElements = mapChildrenToVariationElements(children);
    // // Find the name in the hash
    // const variationChildElement = variationElements[variation.get('name')];
    // if (!variationChildElement) {
    //   return null;
    // }

    // Inject the helper `handleWin` into the child element
    return React.cloneElement(selectedChild, {
      experiment,
      variation,
      id:   variation.get('id'),
      name: variation.get('name'),
    });
  }
}

/**
 * Helper function: Convert `children` to a hash of { `name` => variation }
 */
const mapChildrenToVariationElements = (children) => {
  const variationElements = {};
  React.Children.forEach(children, element => variationElements[element.props.name] = element);
  return variationElements;
};
<commit_msg>Removed console log<commit_after>/** @flow */
import React from 'react';
import Immutable from 'immutable';
import { isVariationComponent } from '../../components/variation';
import Variation, { isVariationContainer } from '../../containers/variation';
import selectVariation from '../../utils/select-variation';
import { cacheStore } from '../../utils/create-cache-store';
import getKey from '../../utils/get-key';

type Props = {
  /**
   * Redux State:
   * ```js
   *   reduxAbTest = {
   *     experiments: [],
   *     active: {
   *       "experiment.name" => "variation.name",
   *       ...
   *     }
   *     ...
   *   }
   * ```
   */
  reduxAbTest: Immutable.Map,
  /**
   * Selector key for choosing an experiment from the redux store.
   * This can be used as such:
   *  <Experiment selector=`Homepage Header Experiment`> ... </Experiment>
   */
  selector: ?string,
  /**
   * ID of the experiment, this is used to pick a specific experient from the redux store.
   * We use getKey to generate the `key` for this experiment.
   */
  id: ?string,
  /**
   * Name of the experiment, this is used to pick a specific experient from the redux store.
   * This should only be used as a fallback if `id` is unavailable
   */
  name: string,
  /**
   * Name of the default variation.
   * >  When defined, this value is used to choose a variation if a stored value is not present.
   * >  This property may be useful for server side rendering but is otherwise not recommended.
   */
  defaultVariationName: ?string,
  /**
   * Action Creator callback: Function({experiment:ExperimentType})
   */
  dispatchActivate: Function,
  /**
   * Action Creator callback: Function({experiment:ExperimentType})
   */
  dispatchDeactivate: Function,
  /**
   * Action Creator callback: Function({experiment:ExperimentType, variation:VariationType})
   */
  dispatchPlay: Function,
  /**
   * Action Creator callback: Function({experiment:ExperimentType, variation:VariationType})
   */
  dispatchWin: Function,
  /**
   * Variation Children to render
   */
  children: any,
};

type State = {
  /**
   * The currenly active experiment
   */
  experiment: ?ExperimentType,
  /**
   * The currenly active variation
   */
  variation: ?VariationType,
  /**
   * The experient was played
   */
  played: ?bool,
};

const isValid = (child) => (isVariationContainer(child) || isVariationComponent(child));

export default class Experiment extends React.Component {
  props: Props;
  state: State;
  static defaultProps = {
    reduxAbTest:          Immutable.Map({}),
    id:                   null,
    name:                 null,
    selector:             null,
    defaultVariationName: null,
    dispatchActivate:     () => {},
    dispatchDeactivate:   () => {},
    dispatchPlay:         () => {},
    dispatchWin:          () => {},
  };
  state = {
    variation:  null,
    experiment: null,
    played:     false,
  };

  /**
   * Activate the variation
   */
  componentWillMount() {
    const { id, name, selector, defaultVariationName, reduxAbTest, dispatchActivate, dispatchPlay } = this.props;
    const experiment = this.getExperiment(reduxAbTest, selector, id, name);

    // If the experiment is unavailable, then record it wasn't played and move on
    if (!experiment) {
      this.setState({ experiment: null, variation: null, played: false });
      return;
    }

    const variation = selectVariation({
      active: reduxAbTest.get('active'),
      experiment,
      defaultVariationName,
      cacheStore
    });

    // These will trigger `componentWillReceiveProps`
    dispatchActivate({experiment});
    dispatchPlay({experiment, variation});
    this.setState({ experiment, variation, played: true });
  }

  /**
   * Update the component's state with the new properties
   */
  componentWillReceiveProps(nextProps:Props) {
    const { selector, id, name, defaultVariationName, reduxAbTest, dispatchActivate, dispatchPlay } = nextProps;
    const experiment = this.getExperiment(reduxAbTest, selector, id, name);
    if (!experiment) {
      // If we no-longer have an experiment anymore, then update the internal state
      if (this.state.experiment) {
        this.setState({ experiment: null, variation: null, played: false });
      }
      return;
    }

    const variation = selectVariation({
      active: reduxAbTest.get('active'),
      experiment,
      defaultVariationName,
      cacheStore
    });

    if (!experiment.equals(this.state.experiment) || !variation.equals(this.state.variation)) {
      // These will trigger `componentWillReceiveProps`
      if (!this.state.played) {
        dispatchActivate({experiment});
        dispatchPlay({experiment, variation});
      }
      this.setState({ experiment, variation, played: true });
    }
  }

  getExperiment(reduxAbTest, selector, id, name) {
    // Find the key of the currently available experiment
    const key = reduxAbTest.getIn(['availableExperiments', selector || id || name], null);
    if (!key) {
      return null;
    }
    // Select the experiment from the redux store
    const experiment = reduxAbTest.get('experiments').find(experiment => (getKey(experiment) === key), null);
    // Return the resulting experiment
    return experiment;
  }

  /**
   * Deactivate the variation from the state
   */
  componentWillUnmount() {
    const { dispatchDeactivate } = this.props;
    const { experiment } = this.state;
    // Dispatch the deactivation event
    if (experiment) {
      dispatchDeactivate({experiment});
    }
  }

  /**
   * Render one of the variations or `null`
   */
  render() {
    const { children } = this.props;
    const { experiment, variation } = this.state;

    const childrenArray = React.Children.toArray(children);

    // If there are no children, render nothing
    if (childrenArray.length === 0) {
      return null;
    }

    // If the first child is text or an unknown component, simply wrap it in a Variation
    if (childrenArray.length === 1 && !isValid(childrenArray[0])) {
      return (
        <Variation id={variation.get('id')} name={variation.get('name')} experiment={experiment} variation={variation}>
          {children}
        </Variation>
      );
    }

    const selectedChild = childrenArray.find( child => (child.props.id === variation.get('id') && variation.get('id')) ) || childrenArray.find( child => (child.props.name === variation.get('name') && variation.get('name')) );
    if (!selectedChild) {
      throw new Error(`Expected to find a Variation child matching id=${variation.get('id')} or name=${variation.get('name')}`);
    }

    // Inject the helper `handleWin` into the child element
    return React.cloneElement(selectedChild, {
      experiment,
      variation,
      id:   variation.get('id'),
      name: variation.get('name'),
    });
  }
}
<|endoftext|>