<commit_before>import React, { Component, PropTypes } from 'react';
import './FilterSidebar.less';
import fuzzysearch from 'fuzzysearch';
import FilterSidebarComponentItem from '../FilterSidebarComponentItem';

export default
class FilterSidebar extends Component {
  constructor(props) {
    super(props);
    this.state = {
      filterInputValue: ''
    };
    this.handleClickOutside = this.handleClickOutside.bind(this);
  }

  componentDidMount() {
    document.body.addEventListener('click', this.handleClickOutside);
    this.refs.searchInput.focus();
  }

  componentWillUnmount() {
    document.body.removeEventListener('click', this.handleClickOutside);
  }

  filterComponentsLists(componentsInfo, filterText) {
    return componentsInfo.filter(component => {
      return fuzzysearch(filterText.toLowerCase(), component.name.toLowerCase())
    })
  }

  handleFilterInputChange(e) {
    this.setState({ filterInputValue: e.target.value })
  }

  collapseBy(list, by) {
    return list.reduce((results, current) => {
      return results.some(result => result[by] === current[by]) ? results : results.concat([current]);
    }, []);
  }

  handleClickOutside(event) {
    if (!this.props.hideOnOutsideClick) {
      return false;
    }
    event.preventDefault();
    let hasParent = (node, parent) => {
      if (node.parentNode) {
        return node.parentNode === parent || hasParent(node.parentNode, parent);
      }
      return false;
    };
    if (event.target !== this._container && !hasParent(event.target, this._container)) {
      this.props.onHide();
    }
    return false;
  }

  render() {
    let preparedComponentsList = this.filterComponentsLists(this.props.componentsInfo, this.state.filterInputValue);
    preparedComponentsList = this.collapseBy(preparedComponentsList, 'name');
    preparedComponentsList = preparedComponentsList.sort(
      (component1, component2) => component1.name > component2.name ? 1 : -1
    );
    return (

 { this._container = container }}
        className="filter-sidebar"
      >

          <input
            className="filter-sidebar__filter-input form-control"
            type="text"
            ref="searchInput"
            placeholder="Component name"
            onChange={(e) => this.handleFilterInputChange(e)}
          />

            {preparedComponentsList.map((component, index) => (
              <FilterSidebarComponentItem
                key={index}
                currentComponent={this.props.currentComponent}
                component={component}
                componentsInfo={this.props.componentsInfo}
                onComponentChange={this.props.onComponentChange}
              />
            ))}

    )
  }
}

FilterSidebar.propTypes = {
  currentComponent: PropTypes.object,
  componentsInfo: PropTypes.arrayOf(PropTypes.object).isRequired,
  onComponentChange: PropTypes.func,
  onHide: PropTypes.func,
  hideOnOutsideClick: PropTypes.bool
};
FilterSidebar.defaultProps = {
  currentComponent: {},
  componentsInfo: [],
  hideOnOutsideClick: false
};
<commit_msg>Implementing hidding sidebar on ESC key.<commit_after>import React, { Component, PropTypes } from 'react';
import './FilterSidebar.less';
import fuzzysearch from 'fuzzysearch';
import FilterSidebarComponentItem from '../FilterSidebarComponentItem';

export default
class FilterSidebar extends Component {
  constructor(props) {
    super(props);
    this.state = {
      filterInputValue: ''
    };
    this.handleClickOutside = this.handleClickOutside.bind(this);
    this.handleKeyDown = this.handleKeyDown.bind(this);
  }

  componentDidMount() {
    document.body.addEventListener('click', this.handleClickOutside);
    window.addEventListener('keydown', this.handleKeyDown);
    this.refs.searchInput.focus();
  }

  componentWillUnmount() {
    document.body.removeEventListener('click', this.handleClickOutside);
    window.removeEventListener('keydown', this.handleKeyDown);
  }

  filterComponentsLists(componentsInfo, filterText) {
    return componentsInfo.filter(component => {
      return fuzzysearch(filterText.toLowerCase(), component.name.toLowerCase())
    })
  }

  handleFilterInputChange(e) {
    this.setState({ filterInputValue: e.target.value })
  }

  handleKeyDown(event) {
    let escKeyCode = 27;
    if(event.keyCode === escKeyCode ) {
      this.props.onHide();
    }
    return false;
  }

  collapseBy(list, by) {
    return list.reduce((results, current) => {
      return results.some(result => result[by] === current[by]) ? results : results.concat([current]);
    }, []);
  }

  handleClickOutside(event) {
    if (!this.props.hideOnOutsideClick) {
      return false;
    }
    event.preventDefault();
    let hasParent = (node, parent) => {
      if (node.parentNode) {
        return node.parentNode === parent || hasParent(node.parentNode, parent);
      }
      return false;
    };
    if (event.target !== this._container && !hasParent(event.target, this._container)) {
      this.props.onHide();
    }
    return false;
  }

  render() {
    let preparedComponentsList = this.filterComponentsLists(this.props.componentsInfo, this.state.filterInputValue);
    preparedComponentsList = this.collapseBy(preparedComponentsList, 'name');
    preparedComponentsList = preparedComponentsList.sort(
      (component1, component2) => component1.name > component2.name ? 1 : -1
    );
    return (

 { this._container = container }}
        className="filter-sidebar"
      >

          <input
            className="filter-sidebar__filter-input form-control"
            type="text"
            ref="searchInput"
            placeholder="Component name"
            onChange={(e) => this.handleFilterInputChange(e)}
          />

            {preparedComponentsList.map((component, index) => (
              <FilterSidebarComponentItem
                key={index}
                currentComponent={this.props.currentComponent}
                component={component}
                componentsInfo={this.props.componentsInfo}
                onComponentChange={this.props.onComponentChange}
              />
            ))}

    )
  }
}

FilterSidebar.propTypes = {
  currentComponent: PropTypes.object,
  componentsInfo: PropTypes.arrayOf(PropTypes.object).isRequired,
  onComponentChange: PropTypes.func,
  onHide: PropTypes.func,
  hideOnOutsideClick: PropTypes.bool
};
FilterSidebar.defaultProps = {
  currentComponent: {},
  componentsInfo: [],
  hideOnOutsideClick: false
};
<|endoftext|>


<commit_before>/* global $, console, angular, Backbone, _ */
angular
	.module('devtools', ['ngRoute', 'ui','indx'])
	.config(['$routeProvider', function ($routeProvider) {
		'use strict';

		$routeProvider
			.when('/', { templateUrl: 'partials/dashboard.html', controller: 'DashboardCtrl' })
			.when('/manifest/:id', { templateUrl: 'partials/manifest.html', controller: 'ManifestCtrl' })
			.when('/manifest/:id/:section', { templateUrl: 'partials/manifest.html', controller: 'ManifestCtrl' })
			.otherwise({ redirectTo: '/' });
	}])
	.service('manifestsService', function ($rootScope, utils, client) {
		'use strict';

		var u = utils,
			box,
			testrunner;

		var testModes = {
			failed: ['Tests failed', 'Some tests have failed'],
			passed: ['Tests passed', 'All tests passed'],
			notrun: ['Tests not run', 'Tests have not yet been run'],
			running: ['Tests running', 'Tests are currently running'],
			none: ['No tests', 'No tests have been provided']
		};

		var Test = Backbone.Model.extend({
			initialize: function (attributes, options) {
				var that = this;
				this.manifest = options.manifest;
				this.on('change', function () {
					console.log('changed', that.toJSON())
					that.haveBeenRun = that.get('have_been_run');
					that.isStarted = that.get('started');
					that.mode =
						that.isAvailable && !that.isRunning ?
							(that.haveBeenRun ?
								(that.results.failures > 0 ? 'failed' : 'passed') :
								'notrun') :
							that.isRunning ? 'running' : 'none'
					that.modeDescription = testModes[that.mode];
					//that.getResults();
				});
				this.trigger('change');
				this.params = this.get('params');
				_.each(this.params, function (param) {
					param.value = param['default'];
				});
				/*box.getObj('test-' + this.manifest.get('name')).then(function (obj) {
					that.obj = obj;
					obj.on('change', function () {
						console.log('test obj change', obj);
					});
				});*/
				//this.getResults();
			},
			start: function (singleRun) {
				var that = this,
					promise = $.Deferred();

				box.getObj(this.obj.id + '-instance-' + u.uuid()).then(function (instance) {
					instance.save({
						'invoked': [true],
						'singleRun': [!!singleRun],
						'params': [JSON.stringify(that.params)]
					}).then(function () {
						var instances = that.obj.get('instances') || [];
						instances.push(instance);
						that.obj.save({
							instances: instances
						}).then(function () {
							promise.resolve();
							u.safeApply($rootScope);
						});
					});
				});

				return promise;

				//this.results = null;
				//this.isRunning = true;
				/*var params = this.paramsStr();
				if (singleRun) {
					params += '&singlerun=' + true;
				}
				$.post('api/start_test?' + params)
					.always(function () {
						//that.isRunning = false;
						u.safeApply($rootScope);
					}).then(function (r) {
						that.set(r.response);
						that.err = false;
						//that.getResults().then(function () {
						//	u.safeApply($rootScope);
						//});
					}).fail(function () {
						that.err = true;
						u.safeApply($rootScope);
					});*/
			},
			/*paramsStr: function () {
				var params = 'id=' + this.id;
				params += '&manifest_id=' + this.manifest.id;
				if (this.params) {
					var paramsObj = {};
					_.each(this.params, function (param) {
						paramsObj[param.name] = param.value;
					});
					params += '&params=' + JSON.stringify(paramsObj);
				}
				return params;
			},
			stop: function (singleRun) {
				var that = this;
				//this.isRunning = true;
				var params = this.paramsStr();
				$.post('api/stop_test?' + params)
					.always(function () {
						//that.isRunning = false;
						u.safeApply($rootScope);
					}).then(function (r) {
						that.set(r.response);
						that.err = false;
						//that.getResults().then(function () {
						//	u.safeApply($rootScope);
						//});
					}).fail(function () {
						that.err = true;
						u.safeApply($rootScope);
					});
			},*/
			runOnce: function () {
				this.singleRun(true);
			},
			/*getResults: function () {
				var that = this,
					promise = $.Deferred();
				this.isLoading = true;
				$.get(this.get('url'), function (data) {
					var $tests = $(data).find('testsuite');

					that.results = $tests.map(function () {
						var $test = $(this),
							failures = Number($test.attr('failures')),
							errors = Number($test.attr('errors'));

						return {
							name: $test.attr('name'),
							//fullname: $test.attr('name'),
							failures: failures,
							errors: errors,
							tests: Number($test.attr('tests')),
							pass: failures + errors === 0,
							timestamp: $test.attr('timestamp'),
							time: $test.attr('time'),
							testcases: $test.find('testcase').map(function () {
								var $testcase = $(this),
									$failure = $testcase.find('failure');
								return {
									name: $testcase.attr('name'),
									classname: $testcase.attr('classname'),
									time: Number($testcase.attr('time')),
									passed: !$failure.length,
									failed: !!$failure.length,
									failure: $failure.text()
								};
							}).get()
						};
					}).get()[0];

					promise.resolve();
				}).always(function () {
					that.isLoading = false;
				});
				return promise;
			},
			refresh: function () {
				this.results = null;
				this.getResults().then(function () {
					u.safeApply($rootScope);
				});
			}*/
		});

		var Tests = Backbone.Collection.extend({
			model: Test,
			initialize: function (models, options) {
				this.options = options;
			},
			add: function (model, options) {
				var that = this;
				console.log('adding', model)
				if (_.isArray(model)) {
					_.each(model, function (model) {
						that.add(model, options);
					});
					return this;
				}
				if (!(model instanceof Backbone.Model)) {
					console.log('we\'re adding', model)
					model.id = this.length;
					console.log(model)
					model = new this.model(model, this.options);
				}
				return Backbone.Collection.prototype.add.call(this, model, options);
			}
		});

		var documentationModes = {
			'building': ['Building docs', 'Documentation is currently being built'],
			'built': ['Docs available', 'Documentation is available'],
			'notbuilt': ['Docs not built', 'Documentation needs to be built'],
			'none': ['No docs', 'No documentation has been provided']
		};

		var DocumentationModel = Backbone.Model.extend({
			initialize: function (attrs, options) {
				var that = this;
				this.manifest = options.manifest;
				this.on('change', function () {
					that.isBuilt = that.get('built');
					that.mode =
						that.isAvailable ?
							(	that.isBuilding ? 'building' :
								that.isBuilt ? 'built' : 'notbuilt' ) : 'none'
					that.modeDescription = documentationModes[that.mode];
				});
				var raw = this.manifest.get('documentation');
				this.isAvailable = !!raw;
				this.docUrl = this.get('url');
				this.set(raw, { silent: true }).trigger('change');
			},
			build: function () {
				var that = this;
				this.isBuilding = true;
				$.post('api/build_doc?id=' + this.manifest.id)
					.then(function (r) {
						that.set(r.response);
						that.refresh();
						that.err = false;
						u.safeApply($rootScope);
					})
					.fail(function (r, s, err) {
						that.err = r.status + ' - ' + err;
						u.safeApply($rootScope);
					})
					.always(function () {
						that.isBuilding = false;
						u.safeApply($rootScope);
					});
			},
			refresh: function () {
				this.set('url', this.docUrl + '?_r' + Math.round(Math.random() * 1e6));
			}
		});

		var Manifest = Backbone.Model.extend({
			defaults: {
				icons: {
					'128': '/apps/devtools/icons/default.png',
				}
			},
			initialize: function () {
				this.documentation = new DocumentationModel(undefined, { manifest: this });
				this.tests = new Tests(this.get('tests'), { manifest: this });
				this.icon = this.get('icons')['128'];
			}
		});

		var Manifests = Backbone.Collection.extend({
			model: Manifest,
			url: 'api/manifests',
			initialize: function () {
				var that = this;
				this.on('add remove reset', function () {
					that.apps = that.where({ type: 'app' });
					that.core = that.where({ type: 'core' });
					that.hasFetched = true;
				});
			},
			parse: function (r) {
				return r.response;
			}
		});

		window.$scope = $rootScope;

		window.resizeIFrame = function (iframe, noEvents) {
			var top = $('.tab-pane nav').offset().top + $('.tab-pane nav').height(),
				wHeight = $(window).innerHeight();
			console.log('t', iframe, top, wHeight)
			$(iframe).css({ 'height': wHeight - top - 6 });
			if (!noEvents) {
				$(window).resize(function () {
					window.resizeIFrame(iframe, true);
				});
			}
		};

		var manifests = new Manifests();

		var start = function () {
			manifests.fetch().then(function () {
				var promises = [];
				// populate the testrunner object
				manifests.each(function (manifest) {
					manifest.tests.each(function (test) {
						var promise = $.Deferred(),
							obj = _(testrunner.get('tests')).find(function (obj) {
								return ((obj.get('manifest_id') || [])[0] === manifest.id) &&
									((obj.get('test_id') || [])[0] === test.id);
							});
						if (obj) {
							promise.resolve();
						} else {
							console.log('test', manifest.id, test.id, '  NOT FOUND')
							box.getObj('test-' + manifest.id + '-' + test.id).then(function (_obj) {
								obj = _obj;
								obj.save({
									manifest_id: [manifest.id],
									test_id: [test.id],
									instances: []
								}).then(function () {
									testrunner.get('tests').push(obj);
									testrunner.save().then(function () {
										promise.resolve();
									});
								});
							});
						}
						promise.then(function () {
							test.obj = obj;
						});
						promises.push(promise);
					});
				});
				$.when.apply($, promises).then(function () {
					manifests.fetched = true;
					u.safeApply($rootScope);
				});
			});
		};

		var loadBox = function(bid) {
			console.log('LOAD BOX', bid)
			client.store.getBox(bid).then(function (_box) {
				console.log('got box >> ', _box.id);
				box = _box;
				window.box = box;
				box.getObj('testrunner').then(function (_testrunner) {
					testrunner = _testrunner;
					console.log('GOT testrunner', testrunner.get('tests'))
					if (!testrunner.has('tests')) {
						testrunner.set({ tests: [] });
					}
					start();
				});
			});
		};
		var init = function() {
			console.log('change on box and user --- init >> ', $rootScope.selectedBox, $rootScope.selectedUser);
			if ($rootScope.selectedUser && $rootScope.selectedBox) {
				loadBox($rootScope.selectedBox);
			}
		};
		$rootScope.$watch('selectedBox + selectedUser', init);
		window.store = client.store;

		window.manifests = manifests;
		$rootScope.breadcrumbs = [{ name: 'Dashboard', url: '#' }]

		return {
			manifests: manifests
		};
	})
	.controller('DashboardCtrl', function ($rootScope, $scope, manifestsService) {
		'use strict';

		$scope.manifests = manifestsService.manifests
		$rootScope.breadcrumbs = [$rootScope.breadcrumbs[0]]
	}).controller('ManifestCtrl', function ($rootScope, $scope, $location, manifestsService, $routeParams) {
		'use strict';

		var ready = $.Deferred();

		if (manifestsService.manifests.fetched) {
			ready.resolve();
		} else {
			manifestsService.manifests.once('sync', function () {
				ready.resolve();
			});
		}

		ready.then(function () {
			var manifest = manifestsService.manifests.get($routeParams.id);
			if (!manifest) {
				$location.path('/');
			}
			var panes = ['overview', 'documentation', 'tests'],
				pane = 'overview';
			if (panes.indexOf($routeParams.section) > -1) {
				pane = $routeParams.section;
			}

			$scope.manifest = manifest;
			$scope.pane = pane;
			$rootScope.breadcrumbs = [$rootScope.breadcrumbs[0], { name: manifest.get('name'), url: '#/manifest/' + $routeParams.id }]
		});
	});
<commit_msg>tests statuses on dashboard<commit_after>/* global $, console, angular, Backbone, _ */
angular
	.module('devtools', ['ngRoute', 'ui','indx'])
	.config(['$routeProvider', function ($routeProvider) {
		'use strict';

		$routeProvider
			.when('/', { templateUrl: 'partials/dashboard.html', controller: 'DashboardCtrl' })
			.when('/manifest/:id', { templateUrl: 'partials/manifest.html', controller: 'ManifestCtrl' })
			.when('/manifest/:id/:section', { templateUrl: 'partials/manifest.html', controller: 'ManifestCtrl' })
			.otherwise({ redirectTo: '/' });
	}])
	.service('manifestsService', function ($rootScope, utils, client) {
		'use strict';

		var u = utils,
			box,
			testrunner;

		var testModes = {
			failed: ['Tests failed', 'Some tests have failed'],
			passed: ['Tests passed', 'All tests passed'],
			notrun: ['Tests not run', 'Tests have not yet been run'],
			running: ['Tests running', 'Tests are currently running'],
			none: ['No tests', 'No tests have been provided']
		};

		var Test = Backbone.Model.extend({
			initialize: function (attributes, options) {
				var that = this;
				this.manifest = options.manifest;
				this.on('change', function () {
					console.log('changed', that.toJSON())
					that.haveBeenRun = that.get('have_been_run');
					that.isStarted = that.get('started');
					that.mode =
						that.isRunning ? 'running' :
							(that.haveBeenRun ?
								(that.results.failures > 0 ? 'failed' : 'passed') :
								'notrun');
					that.modeDescription = testModes[that.mode];
					//that.getResults();
				});
				this.trigger('change');
				this.params = this.get('params');
				_.each(this.params, function (param) {
					param.value = param['default'];
				});
				/*box.getObj('test-' + this.manifest.get('name')).then(function (obj) {
					that.obj = obj;
					obj.on('change', function () {
						console.log('test obj change', obj);
					});
				});*/
				//this.getResults();
			},
			start: function (singleRun) {
				var that = this,
					promise = $.Deferred();

				box.getObj(this.obj.id + '-instance-' + u.uuid()).then(function (instance) {
					instance.save({
						'invoked': [true],
						'singleRun': [!!singleRun],
						'params': [JSON.stringify(that.params)]
					}).then(function () {
						var instances = that.obj.get('instances') || [];
						instances.push(instance);
						that.obj.save({
							instances: instances
						}).then(function () {
							promise.resolve();
							u.safeApply($rootScope);
						});
					});
				});

				return promise;

				//this.results = null;
				//this.isRunning = true;
				/*var params = this.paramsStr();
				if (singleRun) {
					params += '&singlerun=' + true;
				}
				$.post('api/start_test?' + params)
					.always(function () {
						//that.isRunning = false;
						u.safeApply($rootScope);
					}).then(function (r) {
						that.set(r.response);
						that.err = false;
						//that.getResults().then(function () {
						//	u.safeApply($rootScope);
						//});
					}).fail(function () {
						that.err = true;
						u.safeApply($rootScope);
					});*/
			},
			/*paramsStr: function () {
				var params = 'id=' + this.id;
				params += '&manifest_id=' + this.manifest.id;
				if (this.params) {
					var paramsObj = {};
					_.each(this.params, function (param) {
						paramsObj[param.name] = param.value;
					});
					params += '&params=' + JSON.stringify(paramsObj);
				}
				return params;
			},
			stop: function (singleRun) {
				var that = this;
				//this.isRunning = true;
				var params = this.paramsStr();
				$.post('api/stop_test?' + params)
					.always(function () {
						//that.isRunning = false;
						u.safeApply($rootScope);
					}).then(function (r) {
						that.set(r.response);
						that.err = false;
						//that.getResults().then(function () {
						//	u.safeApply($rootScope);
						//});
					}).fail(function () {
						that.err = true;
						u.safeApply($rootScope);
					});
			},*/
			runOnce: function () {
				this.singleRun(true);
			},
			/*getResults: function () {
				var that = this,
					promise = $.Deferred();
				this.isLoading = true;
				$.get(this.get('url'), function (data) {
					var $tests = $(data).find('testsuite');

					that.results = $tests.map(function () {
						var $test = $(this),
							failures = Number($test.attr('failures')),
							errors = Number($test.attr('errors'));

						return {
							name: $test.attr('name'),
							//fullname: $test.attr('name'),
							failures: failures,
							errors: errors,
							tests: Number($test.attr('tests')),
							pass: failures + errors === 0,
							timestamp: $test.attr('timestamp'),
							time: $test.attr('time'),
							testcases: $test.find('testcase').map(function () {
								var $testcase = $(this),
									$failure = $testcase.find('failure');
								return {
									name: $testcase.attr('name'),
									classname: $testcase.attr('classname'),
									time: Number($testcase.attr('time')),
									passed: !$failure.length,
									failed: !!$failure.length,
									failure: $failure.text()
								};
							}).get()
						};
					}).get()[0];

					promise.resolve();
				}).always(function () {
					that.isLoading = false;
				});
				return promise;
			},
			refresh: function () {
				this.results = null;
				this.getResults().then(function () {
					u.safeApply($rootScope);
				});
			}*/
		});

		var Tests = Backbone.Collection.extend({
			model: Test,
			initialize: function (models, options) {
				var that = this;
				this.options = options;
				this.on('change add reset remove', function () {
					var mode = 'none',
						modes = _(that.models).pluck('mode');
					if (modes.indexOf('passed') > -1) { mode = 'passed'; }
					if (modes.indexOf('notrun') > -1) { mode = 'notrun'; }
					if (modes.indexOf('failed') > -1) { mode = 'failed'; }
					if (modes.indexOf('running') > -1) { mode = 'running'; }
					that.mode = mode;
					that.modeDescription = testModes[that.mode];
				});
				this.trigger('change');
			},
			add: function (model, options) {
				var that = this;
				console.log('adding', model)
				if (_.isArray(model)) {
					_.each(model, function (model) {
						that.add(model, options);
					});
					return this;
				}
				if (!(model instanceof Backbone.Model)) {
					console.log('we\'re adding', model)
					model.id = this.length;
					console.log(model)
					model = new this.model(model, this.options);
				}
				return Backbone.Collection.prototype.add.call(this, model, options);
			}
		});

		var documentationModes = {
			'building': ['Building docs', 'Documentation is currently being built'],
			'built': ['Docs available', 'Documentation is available'],
			'notbuilt': ['Docs not built', 'Documentation needs to be built'],
			'none': ['No docs', 'No documentation has been provided']
		};

		var DocumentationModel = Backbone.Model.extend({
			initialize: function (attrs, options) {
				var that = this;
				this.manifest = options.manifest;
				this.on('change', function () {
					that.isBuilt = that.get('built');
					that.mode =
						that.isAvailable ?
							(	that.isBuilding ? 'building' :
								that.isBuilt ? 'built' : 'notbuilt' ) : 'none'
					that.modeDescription = documentationModes[that.mode];
				});
				var raw = this.manifest.get('documentation');
				this.isAvailable = !!raw;
				this.docUrl = this.get('url');
				this.set(raw, { silent: true }).trigger('change');
			},
			build: function () {
				var that = this;
				this.isBuilding = true;
				$.post('api/build_doc?id=' + this.manifest.id)
					.then(function (r) {
						that.set(r.response);
						that.refresh();
						that.err = false;
						u.safeApply($rootScope);
					})
					.fail(function (r, s, err) {
						that.err = r.status + ' - ' + err;
						u.safeApply($rootScope);
					})
					.always(function () {
						that.isBuilding = false;
						u.safeApply($rootScope);
					});
			},
			refresh: function () {
				this.set('url', this.docUrl + '?_r' + Math.round(Math.random() * 1e6));
			}
		});

		var Manifest = Backbone.Model.extend({
			defaults: {
				icons: {
					'128': '/apps/devtools/icons/default.png',
				}
			},
			initialize: function () {
				this.documentation = new DocumentationModel(undefined, { manifest: this });
				this.tests = new Tests(this.get('tests'), { manifest: this });
				this.icon = this.get('icons')['128'];
			}
		});

		var Manifests = Backbone.Collection.extend({
			model: Manifest,
			url: 'api/manifests',
			initialize: function () {
				var that = this;
				this.on('add remove reset', function () {
					that.apps = that.where({ type: 'app' });
					that.core = that.where({ type: 'core' });
					that.hasFetched = true;
				});
			},
			parse: function (r) {
				return r.response;
			}
		});

		window.$scope = $rootScope;

		window.resizeIFrame = function (iframe, noEvents) {
			var top = $('.tab-pane nav').offset().top + $('.tab-pane nav').height(),
				wHeight = $(window).innerHeight();
			console.log('t', iframe, top, wHeight)
			$(iframe).css({ 'height': wHeight - top - 6 });
			if (!noEvents) {
				$(window).resize(function () {
					window.resizeIFrame(iframe, true);
				});
			}
		};

		var manifests = new Manifests();

		var start = function () {
			manifests.fetch().then(function () {
				var promises = [];
				// populate the testrunner object
				manifests.each(function (manifest) {
					manifest.tests.each(function (test) {
						var promise = $.Deferred(),
							obj = _(testrunner.get('tests')).find(function (obj) {
								return ((obj.get('manifest_id') || [])[0] === manifest.id) &&
									((obj.get('test_id') || [])[0] === test.id);
							});
						if (obj) {
							promise.resolve();
						} else {
							console.log('test', manifest.id, test.id, '  NOT FOUND')
							box.getObj('test-' + manifest.id + '-' + test.id).then(function (_obj) {
								obj = _obj;
								obj.save({
									manifest_id: [manifest.id],
									test_id: [test.id],
									instances: []
								}).then(function () {
									testrunner.get('tests').push(obj);
									testrunner.save().then(function () {
										promise.resolve();
									});
								});
							});
						}
						promise.then(function () {
							test.obj = obj;
						});
						promises.push(promise);
					});
				});
				$.when.apply($, promises).then(function () {
					manifests.fetched = true;
					u.safeApply($rootScope);
				});
			});
		};

		var loadBox = function(bid) {
			console.log('LOAD BOX', bid)
			client.store.getBox(bid).then(function (_box) {
				console.log('got box >> ', _box.id);
				box = _box;
				window.box = box;
				box.getObj('testrunner').then(function (_testrunner) {
					testrunner = _testrunner;
					console.log('GOT testrunner', testrunner.get('tests'))
					if (!testrunner.has('tests')) {
						testrunner.set({ tests: [] });
					}
					start();
				});
			});
		};
		var init = function() {
			console.log('change on box and user --- init >> ', $rootScope.selectedBox, $rootScope.selectedUser);
			if ($rootScope.selectedUser && $rootScope.selectedBox) {
				loadBox($rootScope.selectedBox);
			}
		};
		$rootScope.$watch('selectedBox + selectedUser', init);
		window.store = client.store;

		window.manifests = manifests;
		$rootScope.breadcrumbs = [{ name: 'Dashboard', url: '#' }]

		return {
			manifests: manifests
		};
	})
	.controller('DashboardCtrl', function ($rootScope, $scope, manifestsService) {
		'use strict';

		$scope.manifests = manifestsService.manifests
		$rootScope.breadcrumbs = [$rootScope.breadcrumbs[0]]
	}).controller('ManifestCtrl', function ($rootScope, $scope, $location, manifestsService, $routeParams) {
		'use strict';

		var ready = $.Deferred();

		if (manifestsService.manifests.fetched) {
			ready.resolve();
		} else {
			manifestsService.manifests.once('sync', function () {
				ready.resolve();
			});
		}

		ready.then(function () {
			var manifest = manifestsService.manifests.get($routeParams.id);
			if (!manifest) {
				$location.path('/');
			}
			var panes = ['overview', 'documentation', 'tests'],
				pane = 'overview';
			if (panes.indexOf($routeParams.section) > -1) {
				pane = $routeParams.section;
			}

			$scope.manifest = manifest;
			$scope.pane = pane;
			$rootScope.breadcrumbs = [$rootScope.breadcrumbs[0], { name: manifest.get('name'), url: '#/manifest/' + $routeParams.id }]
		});
	});
<|endoftext|>


<commit_before>if (!process.env.token) {
    console.log('Error: Specify token in environment');
    process.exit(1);
}

var Botkit = require('./lib/Botkit.js'),
    os = require('os'),
    controller = Botkit.slackbot({
        //debug: true
    }),
    bot = controller.spawn({
        token: process.env.token
    }).startRTM(),
    request = require('request-promise'),
    Promise = require('bluebird');

controller.setupWebserver(process.env.PORT, function(err, webserver) {
  controller.createWebhookEndpoints(controller.webserver, 'PsRh1Hn3lbVjQpYtf3UaLwKH');
});

var games = {},
    suitMappings = {'HEARTS': 'red', 'SPADES': 'green', 'CLUBS': 'yellow', 'DIAMONDS': 'blue'},
    valueMappings = {'JACK': 'Draw 2', 'QUEEN': 'Skip', 'KING': 'Reverse'};

//TODO: Allow for commands via @mentions as well

controller.hears('new', ['slash_command'/*, 'direct_mention', 'mention'*/], function(bot, message){
    console.log(message);
    initializeGame(bot, message);
});

controller.hears('join', ['slash_command'/*, 'direct_mention', 'mention'*/], function(bot, message){
    console.log(message);
    joinGame(bot, message);
});

controller.hears('quit', ['slash_command'/*, 'direct_mention', 'mention'*/], function(bot, message){
    console.log(message);
    quitGame(bot, message);
});

controller.hears('order', ['slash_command'/*, 'direct_mention', 'mention'*/], function(bot, message){
    console.log(message);
    reportTurnOrder(bot, message, true);
});

controller.hears('setup', ['slash_command'/*, 'direct_mention', 'mention'*/], function(bot, message){
    console.log(message);
    for (var i = 2; i <= 5; i++){
        var mockUser = 'Player' + i;

        joinGame(bot, message, mockUser);
    }
});

controller.hears('start', ['slash_command'], function(bot, message){
    console.log(message);
    beginGame(bot, message);
});

function beginGame(bot, message){
    var user = message.user_name,
        game = getGame(bot, message);

    if (!game){
        return;
    }

    if (game.player1 !== user){
        console.log(game);
        bot.replyPrivate(message, 'Only player 1 (' + game.player1 + ') can start the game.');
        return;
    }

    console.log(Object.keys(game.players).length + ' players at beginning of game.');

    if (Object.keys(game.players).length < 2){
        console.log(game);
        bot.replyPrivate(message, 'You need at least two players to begin playing.');
        return;
    }

    if (game.started){
        console.log(game);
        bot.replyPrivate(message, 'The game is already started.');
        reportTurnOrder(bot, message, true, true);
        return;
    }

    game.started = true;

    //Create the deck
    var deckRequest = request('http://deckofcardsapi.com/api/deck/new/shuffle/?deck_count=2')
        .then(function(result){
            console.log(result);

            game.deckId = result.deck_id;

            console.log(game);

            announceTurn(bot, message);
        });

    Promise.resolve(deckRequest);

    //Deal to each of the players
    for (playerName in game.players){
        var player = game.players[playerName];
        var cardRequest = request('http://deckofcardsapi.com/api/deck/' + game.deckId + '/draw/?count=7')
            .then(function(result){
                console.log(result);

                for (var j = 0; j < result.cards.length; j++){
                    player.cards[j] = getUnoCard(result.cards[j]);
                }
            });

        Promise.resolve(cardRequest);
    }

    announceTurn(bot, message);
}

function getUnoCard(standardCard){
    var value = valueMappings[standardCard.value] || (standardCard.value - 1),
        color = suitMappings[standardCard.suit];

    if (value === 'ACE'){
        color = 'Wild';
        switch (standardCard.suit){
            case 'CLUBS':
            case 'SPADES':
                value = 'Wild';
                break;
            case 'HEARTS':
            case 'DIAMONDS':
                value = 'Draw 4';
                break;
        }
    }

    return {
        color: color,
        value: value
    }
}

function getStandardCard(unoCard){

}

function announceTurn(bot, message){
    var game = getGame(bot, message);

    bot.replyPublic(message, 'It is ' + game.turnOrder[0] + '\'s turn.\nType \\uno cards, \\uno draw, \\uno skip or \\uno play.')
}

function quitGame(bot, message){
    var user = message.user_name,
        game = getGame(bot, message),
        channel = message.channel;

    if (!game.players[user]){
        bot.replyPrivate(message, 'You weren\'t playing to begin with.');
        return;
    }

    delete game.players[user];

    var player = game.turnOrder.indexOf(user);
    game.turnOrder.splice(player, 1);

    bot.replyPublic(message, user + ' has left the game.');

    if (Object.keys(game.players).length === 0){
        bot.replyPublicDelayed(message, 'No more players. Ending the game.');
        games[channel] = newGame();
        return;
    }

    if (game.player1 === user){
        game.player1 = Object.keys(game.players)[0];
        bot.replyPublicDelayed(message, game.player1 + ' is the new player 1.');
    }

    if (Object.keys(game.players).length === 1){
        game.started = false;
        bot.replyPublicDelayed(message, 'Only one player remaining. Waiting for more players.');
    }

    reportTurnOrder(bot, message);
}

function joinGame(bot, message, userName){
    var user = userName || message.user_name,
        channel = message.channel;

    var game = getGame(bot, message);

    if (!game){
        return;
    }

    if (game.players[user]){
        bot.replyPrivate(message, user + ', you\'ve already joined the game!');
        return;
    }

    game.players[user] = {
        hand: []
    };
    game.turnOrder[game.turnOrder.length] = user;

    bot.replyPublic(message, user + ' has joined the game.');

    reportTurnOrder(bot, message);
}

function getGame(bot, message, suppressReport){
    var channel = message.channel;

    if (!games[channel] || !games[channel].initialized){
        if (!suppressReport)
        {
            bot.replyPrivate(message, 'There is no game yet.');
        }
        return undefined;
    }

    return games[channel];
}

function reportTurnOrder(bot, message, isPrivate, isDelayed){
    var game = getGame(bot, message);

    if (!game){
        return;
    }

    var currentOrder = '';

    for (var i = 1; i < game.turnOrder.length + 1; i++){
        if (i > 1){
            currentOrder = currentOrder + ', ';
        }

        currentOrder = currentOrder + '\n' + i + '. ' + game.turnOrder[i - 1];
    }

    if (isPrivate){
        if (isDelayed){
            bot.replyPrivateDelayed(message, 'Current playing order:\n' + currentOrder);
        } else{
            bot.replyPrivate(message, 'Current playing order:\n' + currentOrder);
        }
    }
    else{
        bot.replyPublicDelayed(message, 'Current playing order:\n' + currentOrder);
    }
}

function initializeGame(bot, message){
    var user = message.user_name,
        channel = message.channel;

    var game = getGame(bot, message, true);

    if (!game){
        game = newGame();

        games[channel] = game;
    }

    if (game.initialized){
        bot.replyPrivate(message, 'There is already an uno game in progress. Type !join to join the game.');
        return;
    }

    game = newGame();
    games[channel] = game;

    game.initialized = true;
    game.player1 = user;
    game.players[user] = {
        hand: []
    };
    game.turnOrder[game.turnOrder.length] = user;

    bot.replyPublic(message, user + ' has started UNO. Type !join to join the game.');

    reportTurnOrder(bot, message);
};

function newGame(){
    return {
        initialized: false,
        started: false,
        players: [],
        deckId: '',
        turnOrder: []
    };
}

controller.hears(['uptime', 'identify yourself', 'who are you', 'what is your name'],
    'direct_message,direct_mention,mention', function(bot, message) {

        var hostname = os.hostname();
        var uptime = formatUptime(process.uptime());

        bot.reply(message,
            ':robot_face: I am a bot named <@' + bot.identity.name +
             '>. I have been running for ' + uptime + ' on ' + hostname + '.');

    });

function formatUptime(uptime) {
    var unit = 'second';
    if (uptime > 60) {
        uptime = uptime / 60;
        unit = 'minute';
    }
    if (uptime > 60) {
        uptime = uptime / 60;
        unit = 'hour';
    }
    if (uptime != 1) {
        unit = unit + 's';
    }

    uptime = uptime + ' ' + unit;
    return uptime;
}
<commit_msg>Fix deck/drawing promises<commit_after>if (!process.env.token) {
    console.log('Error: Specify token in environment');
    process.exit(1);
}

var Botkit = require('./lib/Botkit.js'),
    os = require('os'),
    controller = Botkit.slackbot({
        //debug: true
    }),
    bot = controller.spawn({
        token: process.env.token
    }).startRTM(),
    request = require('request-promise'),
    Promise = require('bluebird');

controller.setupWebserver(process.env.PORT, function(err, webserver) {
  controller.createWebhookEndpoints(controller.webserver, 'PsRh1Hn3lbVjQpYtf3UaLwKH');
});

var games = {},
    suitMappings = {'HEARTS': 'red', 'SPADES': 'green', 'CLUBS': 'yellow', 'DIAMONDS': 'blue'},
    valueMappings = {'JACK': 'Draw 2', 'QUEEN': 'Skip', 'KING': 'Reverse'};

//TODO: Allow for commands via @mentions as well

controller.hears('new', ['slash_command'/*, 'direct_mention', 'mention'*/], function(bot, message){
    console.log(message);
    initializeGame(bot, message);
});

controller.hears('join', ['slash_command'/*, 'direct_mention', 'mention'*/], function(bot, message){
    console.log(message);
    joinGame(bot, message);
});

controller.hears('quit', ['slash_command'/*, 'direct_mention', 'mention'*/], function(bot, message){
    console.log(message);
    quitGame(bot, message);
});

controller.hears('order', ['slash_command'/*, 'direct_mention', 'mention'*/], function(bot, message){
    console.log(message);
    reportTurnOrder(bot, message, true);
});

controller.hears('setup', ['slash_command'/*, 'direct_mention', 'mention'*/], function(bot, message){
    console.log(message);
    for (var i = 2; i <= 5; i++){
        var mockUser = 'Player' + i;

        joinGame(bot, message, mockUser);
    }
});

controller.hears('start', ['slash_command'], function(bot, message){
    console.log(message);
    beginGame(bot, message);
});

function beginGame(bot, message){
    var user = message.user_name,
        game = getGame(bot, message);

    if (!game){
        return;
    }

    if (game.player1 !== user){
        console.log(game);
        bot.replyPrivate(message, 'Only player 1 (' + game.player1 + ') can start the game.');
        return;
    }

    console.log(Object.keys(game.players).length + ' players at beginning of game.');

    if (Object.keys(game.players).length < 2){
        console.log(game);
        bot.replyPrivate(message, 'You need at least two players to begin playing.');
        return;
    }

    if (game.started){
        console.log(game);
        bot.replyPrivate(message, 'The game is already started.');
        reportTurnOrder(bot, message, true, true);
        return;
    }

    game.started = true;

    //Create the deck
    var deckRequest = request('http://deckofcardsapi.com/api/deck/new/shuffle/?deck_count=2')
        .then(function(result){
            console.log(result);

            game.deckId = result.deck_id;

            console.log(game);

            announceTurn(bot, message);
        });

    Promise.all([deckRequest]);

    var drawRequests = [];

    //Deal to each of the players
    for (playerName in game.players){
        var player = game.players[playerName];
        var cardRequest = request('http://deckofcardsapi.com/api/deck/' + game.deckId + '/draw/?count=7')
            .then(function(result){
                console.log(result);

                for (var j = 0; j < result.cards.length; j++){
                    player.cards[j] = getUnoCard(result.cards[j]);
                }
            });

        drawRequests.push(cardRequest);
    }

    Promise.all(drawRequests);

    announceTurn(bot, message);
}

function getUnoCard(standardCard){
    var value = valueMappings[standardCard.value] || (standardCard.value - 1),
        color = suitMappings[standardCard.suit];

    if (value === 'ACE'){
        color = 'Wild';
        switch (standardCard.suit){
            case 'CLUBS':
            case 'SPADES':
                value = 'Wild';
                break;
            case 'HEARTS':
            case 'DIAMONDS':
                value = 'Draw 4';
                break;
        }
    }

    return {
        color: color,
        value: value
    }
}

function getStandardCard(unoCard){

}

function announceTurn(bot, message){
    var game = getGame(bot, message);

    bot.replyPublicDelayed(message, 'It is ' + game.turnOrder[0] + '\'s turn.\nType \\uno cards, \\uno draw, \\uno skip or \\uno play.')
}

function quitGame(bot, message){
    var user = message.user_name,
        game = getGame(bot, message),
        channel = message.channel;

    if (!game.players[user]){
        bot.replyPrivate(message, 'You weren\'t playing to begin with.');
        return;
    }

    delete game.players[user];

    var player = game.turnOrder.indexOf(user);
    game.turnOrder.splice(player, 1);

    bot.replyPublic(message, user + ' has left the game.');

    if (Object.keys(game.players).length === 0){
        bot.replyPublicDelayed(message, 'No more players. Ending the game.');
        games[channel] = newGame();
        return;
    }

    if (game.player1 === user){
        game.player1 = Object.keys(game.players)[0];
        bot.replyPublicDelayed(message, game.player1 + ' is the new player 1.');
    }

    if (Object.keys(game.players).length === 1){
        game.started = false;
        bot.replyPublicDelayed(message, 'Only one player remaining. Waiting for more players.');
    }

    reportTurnOrder(bot, message);
}

function joinGame(bot, message, userName){
    var user = userName || message.user_name,
        channel = message.channel;

    var game = getGame(bot, message);

    if (!game){
        return;
    }

    if (game.players[user]){
        bot.replyPrivate(message, user + ', you\'ve already joined the game!');
        return;
    }

    game.players[user] = {
        hand: []
    };
    game.turnOrder.push(user);

    bot.replyPublic(message, user + ' has joined the game.');

    reportTurnOrder(bot, message);
}

function getGame(bot, message, suppressReport){
    var channel = message.channel;

    if (!games[channel] || !games[channel].initialized){
        if (!suppressReport)
        {
            bot.replyPrivate(message, 'There is no game yet.');
        }
        return undefined;
    }

    return games[channel];
}

function reportTurnOrder(bot, message, isPrivate, isDelayed){
    var game = getGame(bot, message);

    if (!game){
        return;
    }

    var currentOrder = '';

    for (var i = 1; i < game.turnOrder.length + 1; i++){
        if (i > 1){
            currentOrder = currentOrder + ', ';
        }

        currentOrder = currentOrder + '\n' + i + '. ' + game.turnOrder[i - 1];
    }

    if (isPrivate){
        if (isDelayed){
            bot.replyPrivateDelayed(message, 'Current playing order:\n' + currentOrder);
        } else{
            bot.replyPrivate(message, 'Current playing order:\n' + currentOrder);
        }
    }
    else{
        bot.replyPublicDelayed(message, 'Current playing order:\n' + currentOrder);
    }
}

function initializeGame(bot, message){
    var user = message.user_name,
        channel = message.channel;

    var game = getGame(bot, message, true);

    if (!game){
        game = newGame();

        games[channel] = game;
    }

    if (game.initialized){
        bot.replyPrivate(message, 'There is already an uno game in progress. Type !join to join the game.');
        return;
    }

    game = newGame();
    games[channel] = game;

    game.initialized = true;
    game.player1 = user;
    game.players[user] = {
        hand: []
    };
    game.turnOrder.push(user);

    bot.replyPublic(message, user + ' has started UNO. Type !join to join the game.');

    reportTurnOrder(bot, message);
};

function newGame(){
    return {
        initialized: false,
        started: false,
        players: [],
        deckId: '',
        turnOrder: []
    };
}

controller.hears(['uptime', 'identify yourself', 'who are you', 'what is your name'],
    'direct_message,direct_mention,mention', function(bot, message) {

        var hostname = os.hostname();
        var uptime = formatUptime(process.uptime());

        bot.reply(message,
            ':robot_face: I am a bot named <@' + bot.identity.name +
             '>. I have been running for ' + uptime + ' on ' + hostname + '.');

    });

function formatUptime(uptime) {
    var unit = 'second';
    if (uptime > 60) {
        uptime = uptime / 60;
        unit = 'minute';
    }
    if (uptime > 60) {
        uptime = uptime / 60;
        unit = 'hour';
    }
    if (uptime != 1) {
        unit = unit + 's';
    }

    uptime = uptime + ' ' + unit;
    return uptime;
}
<|endoftext|>


<commit_before>var slice = [].slice
var eject = require('eject')

// todo: create strings parser.
function Interrupt () {
    this._types = {}
}

Interrupt.prototype._populate = function (error, type, vargs) {
    this._types[type] || (this._types[type] = {})
    error.message = error.type = type
    error.typeIdentifier = this._types[type]
    var context = error.context = {}
    vargs.forEach(function (values) {
        for (var key in values) {
            context[key] = values[key]
        }
    })
    error.context = context
    return error
}

Interrupt.prototype.error = function (error, type) {
    return this._populate(error, type, slice.call(arguments, 2))
}

Interrupt.prototype.panic = function (error, type) {
    throw this._populate(error, type, slice.call(arguments, 2))
}

Interrupt.prototype.type = function (error) {
    if (error.type
        && this._types[error.type]
        && this._types[error.type] === error.typeIdentifier
    ) {
        return error.type
    }
    return null
}

Interrupt.prototype.rescue = function (catcher, callback) {
    callback || (callback = eject)
    return function (error) {
        if (error) {
            if (this.type(error) != null) {
                catcher(error)
            } else {
                callback(error)
            }
        }
    }.bind(this)
}

module.exports = function (messages) {
    return new Interrupt
}
<commit_msg>Return catcher value, give catcher the callback.<commit_after>var slice = [].slice
var eject = require('eject')

// todo: create strings parser.
function Interrupt () {
    this._types = {}
}

Interrupt.prototype._populate = function (error, type, vargs) {
    this._types[type] || (this._types[type] = {})
    error.message = error.type = type
    error.typeIdentifier = this._types[type]
    var context = error.context = {}
    vargs.forEach(function (values) {
        for (var key in values) {
            context[key] = values[key]
        }
    })
    error.context = context
    return error
}

Interrupt.prototype.error = function (error, type) {
    return this._populate(error, type, slice.call(arguments, 2))
}

Interrupt.prototype.panic = function (error, type) {
    throw this._populate(error, type, slice.call(arguments, 2))
}

Interrupt.prototype.type = function (error) {
    if (error.type
        && this._types[error.type]
        && this._types[error.type] === error.typeIdentifier
    ) {
        return error.type
    }
    return null
}

Interrupt.prototype.rescue = function (catcher, callback) {
    callback || (callback = eject)
    return function (error) {
        if (error) {
            if (this.type(error) != null) {
                return catcher(error, callback)
            } else {
                callback(error)
            }
        }
    }.bind(this)
}

module.exports = function (messages) {
    return new Interrupt
}
<|endoftext|>


<commit_before> // Manipulating JavaScript Objects

// I worked on this challenge: [by myself, with: ]

// There is a section below where you will write your code.

// DO NOT ALTER THIS OBJECT BY ADDING ANYTHING WITHIN THE CURLY BRACES!
var terah = {
  name: "Terah",
  age: 32,
  height: 66,
  weight: 130,
  hairColor: "brown",
  eyeColor: "brown"
}
// __________________________________________
// Write your code below.

// __________________________________________
// Reflection: Use the reflection guidelines
//
//
//
//
//
//

// __________________________________________
// Driver Code:  Do not alter code below this line.
function assert(test, message, test_number) {
  if (!test) {
    console.log(test_number + "false");
    throw "ERROR: " + message;
  }
  console.log(test_number + "true");
  return true;
}

assert(
  (adam instanceof Object),
  "The value of adam should be an Object.",
  "1. "
)

assert(
  (adam.name === "Adam"),
  "The value of the adam name property should be 'Adam'.",
  "2. "
)

assert(
  terah.spouse === adam,
  "terah should have a spouse property with the value of the object adam.",
  "3. "
)

assert(
  terah.weight === 125,
  "The terah weight property should be 125.",
  "4. "
)

assert(
  terah.eyeColor === undefined || null,
  "The terah eyeColor property should be deleted.",
  "5. "
)

assert(
  terah.spouse.spouse === terah,
  "Terah's spouse's spouse property should refer back to the terah object.",
  "6. "
)

assert(
  (terah.children instanceof Object),
  "The value of the terah children property should be defined as an Object.",
  "7. "
)

assert(
  (terah.children.carson instanceof Object),
  "carson should be defined as an object and assigned as a child of Terah",
  "8. "
  )

assert(
  terah.children.carson.name === "Carson",
  "Terah's children should include an object called carson which has a name property equal to 'Carson'.",
  "9. "
)

assert(
  (terah.children.carter instanceof Object),
  "carter should be defined as an object and assigned as a child of Terah",
  "10. "
  )

assert(
  terah.children.carter.name === "Carter",
  "Terah's children should include an object called carter which has a name property equal to 'Carter'.",
  "11. "
)

assert(
  (terah.children.colton instanceof Object),
  "colton should be defined as an object and assigned as a child of Terah",
  "12. "
  )

assert(
  terah.children.colton.name === "Colton",
  "Terah's children should include an object called colton which has a name property equal to 'Colton'.",
  "13. "
)

assert(
  adam.children === terah.children,
  "The value of the adam children property should be equal to the value of the terah children property",
  "14. "
)

console.log("\nHere is your final terah object:")
console.log(terah)<commit_msg>Add 7.4 for submission<commit_after> // Manipulating JavaScript Objects

// I worked on this challenge: [by myself, with: ]

// There is a section below where you will write your code.

// DO NOT ALTER THIS OBJECT BY ADDING ANYTHING WITHIN THE CURLY BRACES!
var terah = {
  name: "Terah",
  age: 32,
  height: 66,
  weight: 130,
  hairColor: "brown",
  eyeColor: "brown"
}
// __________________________________________
// Write your code below.

var adam = {
  name: "Adam",
}

terah.spouse = adam;

terah.weight =125;

delete terah.eyeColor;

adam.spouse = terah;

terah.children = {
};

terah.children.carson = {};
terah.children.carson.name = "Carson";

terah.children.carter = {};
terah.children.carter.name = "Carter"

terah.children.colton = {}
terah.children.colton.name = "Colton"

adam.children = terah.children
// __________________________________________
/*
Reflection: Use the reflection guidelines
What tests did you have trouble passing? What did you do to make
it pass? Why did that work?
I had a lot of trouble passing the 'define an object called carter'
because I thought it was still carson because it took me a few
minutes to pass that test, and then I was frusterated because it was
the same code, it's right there, you liked it a minute ago. But it
 was the wrong child!! So I changed the name and ta-da! I'll keep
  a closer eye on similar words from now on.

How difficult was it to add and delete properties outside of
 the object itself?
Not Hard, just tricky. I had to look up how to assign
values to nested objects.

What did you learn about manipulating objects in this challenge?
I learned a lot about objects, and how to deal with nested objects.
*/

// __________________________________________
// Driver Code:  Do not alter code below this line.
function assert(test, message, test_number) {
  if (!test) {
    console.log(test_number + "false");
    throw "ERROR: " + message;
  }
  console.log(test_number + "true");
  return true;
}

assert(
  (adam instanceof Object),
  "The value of adam should be an Object.",
  "1. "
)

assert(
  (adam.name === "Adam"),
  "The value of the adam name property should be 'Adam'.",
  "2. "
)

assert(
  terah.spouse === adam,
  "terah should have a spouse property with the value of the object adam.",
  "3. "
)

assert(
  terah.weight === 125,
  "The terah weight property should be 125.",
  "4. "
)

assert(
  terah.eyeColor === undefined || null,
  "The terah eyeColor property should be deleted.",
  "5. "
)

assert(
  terah.spouse.spouse === terah,
  "Terah's spouse's spouse property should refer back to the terah object.",
  "6. "
)

assert(
  (terah.children instanceof Object),
  "The value of the terah children property should be defined as an Object.",
  "7. "
)

assert(
  (terah.children.carson instanceof Object),
  "carson should be defined as an object and assigned as a child of Terah",
  "8. "
  )

assert(
  terah.children.carson.name === "Carson",
  "Terah's children should include an object called carson which has a name property equal to 'Carson'.",
  "9. "
)

assert(
  (terah.children.carter instanceof Object),
  "carter should be defined as an object and assigned as a child of Terah",
  "10. "
  )

assert(
  terah.children.carter.name === "Carter",
  "Terah's children should include an object called carter which has a name property equal to 'Carter'.",
  "11. "
)

assert(
  (terah.children.colton instanceof Object),
  "colton should be defined as an object and assigned as a child of Terah",
  "12. "
  )

assert(
  terah.children.colton.name === "Colton",
  "Terah's children should include an object called colton which has a name property equal to 'Colton'.",
  "13. "
)

assert(
  adam.children === terah.children,
  "The value of the adam children property should be equal to the value of the terah children property",
  "14. "
)

console.log("\nHere is your final terah object:")
console.log(terah)<|endoftext|>


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

const {dbConnect} = require('./db');
const path = require('path');
const sleep = require('sleep-promise');

const RETRY_MILLIS = 60000;
const RETRY_TIMES = 60;

const db = dbConnect();

/**
 * Get a file from the bundle-size directory in the AMPHTML build artifacts
 * repository.
 *
 * @param {github} github an authenticated GitHub API object.
 * @param {string} filename the name of the file to retrieve.
 * @throws {Error} on any error.
 * @return {string} the text contents of the file.
 */
async function getBuildArtifactsFile(github, filename) {
  return await github.repos.getContents({
    owner: 'ampproject',
    repo: 'amphtml-build-artifacts',
    path: path.join('bundle-size', filename),
  }).then(result => {
    return Buffer.from(result.data.content, 'base64').toString();
  });
}

/**
 * Get the list of OWNERS that can approve this PR.
 *
 * @param {github} github an authorized GitHub API object.
 * @throws {Error} on any error.
 * @return {!Array<string>} an array of OWNERS GitHub usernames.
 */
async function getOwners(github) {
  return (await getBuildArtifactsFile(github, 'OWNERS'))
      .trim()
      .split('\n')
      .filter(line => !!line && !line.startsWith('#'));
}

/**
 * Format the bundle size delta in "Δ 99.99KB" format.
 *
 * Always fixed with 2 digits after the dot, preceded with a plus or minus sign.
 *
 * @param {number} delta the bundle size delta.
 * @return {string} formatted bundle size delta.
 */
function formatBundleSizeDelta(delta) {
  return 'Δ ' + (delta >= 0 ? '+' : '') + delta.toFixed(2) + 'KB';
}

module.exports = app => {
  /**
   * Get the GitHub Check object from the database.
   *
   * @param {string} headSha commit SHA of the head commit of a pull request.
   * @return {!Object} GitHub Check object.
   */
  async function getCheckFromDatabase(headSha) {
    const results = await db('checks')
        .select('head_sha', 'pull_request_id', 'installation_id', 'owner',
            'repo', 'check_run_id', 'delta')
        .where('head_sha', headSha);
    if (results.length > 0) {
      return results[0];
    } else {
      return null;
    }
  }

  /**
   * Try to report the bundle size of a pull request to the GitHub check.
   *
   * @param {!Object} check GitHub Check object.
   * @param {string} baseSha commit SHA of the base commit being compared to.
   * @param {number} bundleSize the total bundle size in KB.
   * @param {boolean} lastAttempt true if this is the last retry.
   * @return {boolean} true if succeeded; false otherwise.
   */
  async function tryReport(check, baseSha, bundleSize, lastAttempt = false) {
    const github = await app.auth(check.installation_id);
    const githubOptions = {
      owner: check.owner,
      repo: check.repo,
    };
    const updatedCheckOptions = Object.assign({
      check_run_id: check.check_run_id,
      name: 'ampproject/bundle-size',
      completed_at: new Date().toISOString(),
    }, githubOptions);

    try {
      const baseBundleSize = parseFloat(
          await getBuildArtifactsFile(github, baseSha));
      const bundleSizeDelta = bundleSize - baseBundleSize;
      const bundleSizeDeltaFormatted = formatBundleSizeDelta(bundleSizeDelta);

      await db('checks')
          .update({delta: bundleSizeDelta})
          .where({head_sha: check.head_sha});

      const requiresApproval =
          bundleSizeDelta > process.env['MAX_ALLOWED_INCREASE'];
      if (requiresApproval) {
        Object.assign(updatedCheckOptions, {
          conclusion: 'action_required',
          output: {
            title: `${bundleSizeDeltaFormatted} | approval required`,
            summary: 'This pull request has increased the bundle size ' +
              '(gzipped compressed size of `v0.js`) by ' +
              `${bundleSizeDeltaFormatted}. As part of an ongoing effort to ` +
              'reduce the bundle size, this change requires special approval.' +
              '\n' +
              'A member of the bundle-size group will be added automatically ' +
              'to review this PR. Only once the member approves this PR, ' +
              'can it be merged. If you do not receive a response from the ' +
              'group member, feel free to tag another person listed in the ' +
              'bundle-size [OWNERS](https://github.com/ampproject/' +
              'amphtml-build-artifacts/blob/master/bundle-size/OWNERS) file.',
          },
        });
      } else {
        Object.assign(updatedCheckOptions, {
          conclusion: 'success',
          output: {
            title: `${bundleSizeDeltaFormatted} | no approval necessary`,
            summary: 'This pull request does not increase the bundle size ' +
              '(gzipped compressed size of `v0.js`) by any significant ' +
              'amount, so no special approval is necessary. The bundle size ' +
              `change is ${bundleSizeDeltaFormatted}`,
          },
        });
      }
      await github.checks.update(updatedCheckOptions);

      if (requiresApproval) {
        await addOwnersReviewer(github,
            Object.assign({number: check.pull_request_id}, githubOptions));
      }

      return true;
    } catch (error) {
      const partialHeadSha = check.head_sha.substr(0, 7);
      const partialBaseSha = baseSha.substr(0, 7);
      app.log('ERROR: Failed to retrieve the bundle size of ' +
              `${partialHeadSha} (PR #${check.pull_request_id}) with branch ` +
              `point ${partialBaseSha} from GitHub: ${error}`);
      if (lastAttempt) {
        app.log('No more retries left. Reporting failure');
        Object.assign(updatedCheckOptions, {
          conclusion: 'action_required',
          output: {
            title: 'Failed to retrieve the bundle size of branch point ' +
                partialBaseSha,
            summary: 'The bundle size (gzipped compressed size of `v0.js`) ' +
              'of this pull request could not be determined because the base ' +
              'size (that is, the bundle size of the `master` commit that ' +
              'this pull request was compared against) was not found in the ' +
              '`https://github.com/ampproject/amphtml-build-artifacts` ' +
              'repository. This can happen due to failed or delayed Travis ' +
              'builds on said `master` commit.\n' +
              'A member of the bundle-size group will be added automatically ' +
              'to review this PR. Only once the member approves this PR, ' +
              'can it be merged. If you do not receive a response from the ' +
              'group member, feel free to tag another person listed in the ' +
              'bundle-size [OWNERS](https://github.com/ampproject/' +
              'amphtml-build-artifacts/blob/master/bundle-size/OWNERS) file.',
          },
        });
        await github.checks.update(updatedCheckOptions);
        await addOwnersReviewer(github,
            Object.assign({number: check.pull_request_id}, githubOptions));
      }
      return false;
    }
  }

  /**
   * Add an OWNERS reviewer to the pull request.
   *
   * Ignore errors as this is a non-critical action.
   *
   * @param {github} github an authenticated GitHub API object.
   * @param {!Object} pullRequest GitHub Pull Request object.
   */
  async function addOwnersReviewer(github, pullRequest) {
    try {
      const owners = await getOwners(github);
      const reviewersResponse = await github.pullRequests.listReviewRequests(
          pullRequest);
      const reviewers = new Set(
          reviewersResponse.data.users.map(user => user.login));
      if (owners.some(owner => reviewers.has(owner))) {
        app.log(`INFO: Pull request ${pullRequest.number} already has an ` +
                'OWNERS reviewer. Skipping...');
        return;
      }

      // Choose a random OWNERS user and add them as a reviewer to the pull
      // request.
      const newReviewer = owners[Math.floor(Math.random() * owners.length)];
      return await github.pullRequests.createReviewRequest(Object.assign({
        reviewers: [newReviewer],
      }, pullRequest));
    } catch (error) {
      app.log('ERROR: Failed to add a reviewer to pull request ' +
              `${pullRequest.number}. Skipping...`);
      app.log(`Error message: ${error}`);
    }
  }

  app.on(['pull_request.opened', 'pull_request.synchronize'], async context => {
    context.log(`Pull request ${context.payload.number} created/updated`);

    const headSha = context.payload.pull_request.head.sha;
    const params = context.repo({
      name: 'ampproject/bundle-size',
      head_sha: headSha,
      output: {
        title: 'Calculating new bundle size for this PR…',
        summary: 'The bundle size (gzipped compressed size of `v0.js`) ' +
          'of this pull request is being calculated on Travis. Look for the ' +
          'shard that contains "Bundle Size" its title.',
      },
    });
    const check = await context.github.checks.create(params);

    const checkRunId = check.data.id;
    await db.transaction(trx => {
      return trx('checks')
          .first('head_sha')
          .where('head_sha', headSha)
          .then(existingRow => {
            if (existingRow === undefined) {
              return trx('checks').insert({
                head_sha: headSha,
                pull_request_id: context.payload.number,
                installation_id: context.payload.installation.id,
                owner: params.owner,
                repo: params.repo,
                check_run_id: checkRunId,
              });
            } else {
              return trx('checks')
                  .update({check_run_id: checkRunId})
                  .where({head_sha: headSha});
            }
          }).then(trx.commit).catch(trx.rollback);
    });
  });

  app.on('pull_request_review.submitted', async context => {
    const approver = context.payload.review.user.login;
    const owners = await getOwners(context.github);
    const pullRequestId = context.payload.pull_request.number;
    const headSha = context.payload.pull_request.head.sha;

    if (context.payload.review.state == 'approved' &&
        owners.includes(approver)) {
      context.log(`Pull request ${pullRequestId} approved by a bundle-size ` +
          'keeper');

      const check = await getCheckFromDatabase(headSha);
      if (!check) {
        context.log(`Check ID for pull request ${pullRequestId} with head ` +
            `SHA ${headSha} was not found in the database`);
        return;
      }

      let approvalMessagePrefix;
      if (check.delta === null) {
        approvalMessagePrefix = 'Δ ±?.??KB';
      } else {
        const bundleSizeDelta = parseFloat(check.delta);
        if (bundleSizeDelta <= process.env['MAX_ALLOWED_INCREASE']) {
          return;
        }
        approvalMessagePrefix = formatBundleSizeDelta(bundleSizeDelta);
      }

      await context.github.checks.update({
        owner: check.owner,
        repo: check.repo,
        check_run_id: check.check_run_id,
        conclusion: 'success',
        completed_at: new Date().toISOString(),
        output: {
          title: `${approvalMessagePrefix} | approved by @${approver}`,
          summary: 'The bundle size (gzipped compressed size of `v0.js`) ' +
            `of this pull request was approved by ${approver}`,
        },
      });
    }
  });

  const v0 = app.route('/v0');
  v0.use((request, response, next) => {
    request.app.set('trust proxy', true);
    if ('TRAVIS_IP_ADDRESSES' in process.env &&
        !process.env['TRAVIS_IP_ADDRESSES'].includes(request.ip)) {
      app.log(`Refused a request to ${request.originalUrl} from ${request.ip}`);
      response.status(403).end('You are not Travis!');
    } else {
      next();
    }
  });
  v0.use(require('express').json());

  v0.post('/commit/:headSha/skip', async (request, response) => {
    const {headSha} = request.params;
    app.log(`Marking SHA ${headSha} for skip`);

    const check = await getCheckFromDatabase(headSha);
    if (!check) {
      return response.status(404).end(`${headSha} not in database`);
    }
    const github = await app.auth(check.installation_id);
    await github.checks.update({
      owner: check.owner,
      repo: check.repo,
      check_run_id: check.check_run_id,
      conclusion: 'neutral',
      completed_at: new Date().toISOString(),
      output: {
        title: 'check skipped because PR contains no runtime changes',
        summary: 'An automated check has determined that the bundle size ' +
          '(gzipped compressed size of `v0.js`) could not be affected by ' +
          'the files that this pull request modifies, so this check was ' +
          'marked as skipped.',
      },
    });
    response.end();
  });

  v0.post('/commit/:headSha/report', async (request, response) => {
    if (!('baseSha' in request.body) ||
        typeof request.body.baseSha != 'string' ||
        !/^[0-9a-f]{40}$/.test(request.body.baseSha)) {
      return response.status(400).end(
          'POST request to /report must have commit SHA field "baseSha"');
    }
    if (!('bundleSize' in request.body) ||
        typeof request.body.bundleSize != 'number') {
      return response.status(400).end(
          'POST request to /report must have numeric field "bundleSize"');
    }
    const {headSha} = request.params;
    const {baseSha, bundleSize} = request.body;

    const check = await getCheckFromDatabase(headSha);
    if (!check) {
      return response.status(404).end(`${headSha} not in database`);
    }

    let reportSuccess = await tryReport(check, baseSha, bundleSize);
    if (reportSuccess) {
      response.end();
    } else {
      response.status(202).end();
      let retriesLeft = RETRY_TIMES - 1;
      do {
        app.log(`Will retry ${retriesLeft} more time(s) in ${RETRY_MILLIS} ms`);
        await sleep(RETRY_MILLIS);
        retriesLeft--;
        reportSuccess = await tryReport(
            check, baseSha, bundleSize, /* lastAttempt */ retriesLeft == 0);
      } while (retriesLeft > 0 && !reportSuccess);
    }
  });
};
<commit_msg>Fix typo introduced in #62 (#63)<commit_after>/**
 * Copyright 2018, the AMP HTML authors
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

const {dbConnect} = require('./db');
const path = require('path');
const sleep = require('sleep-promise');

const RETRY_MILLIS = 60000;
const RETRY_TIMES = 60;

const db = dbConnect();

/**
 * Get a file from the bundle-size directory in the AMPHTML build artifacts
 * repository.
 *
 * @param {github} github an authenticated GitHub API object.
 * @param {string} filename the name of the file to retrieve.
 * @throws {Error} on any error.
 * @return {string} the text contents of the file.
 */
async function getBuildArtifactsFile(github, filename) {
  return await github.repos.getContents({
    owner: 'ampproject',
    repo: 'amphtml-build-artifacts',
    path: path.join('bundle-size', filename),
  }).then(result => {
    return Buffer.from(result.data.content, 'base64').toString();
  });
}

/**
 * Get the list of OWNERS that can approve this PR.
 *
 * @param {github} github an authorized GitHub API object.
 * @throws {Error} on any error.
 * @return {!Array<string>} an array of OWNERS GitHub usernames.
 */
async function getOwners(github) {
  return (await getBuildArtifactsFile(github, 'OWNERS'))
      .trim()
      .split('\n')
      .filter(line => !!line && !line.startsWith('#'));
}

/**
 * Format the bundle size delta in "Δ 99.99KB" format.
 *
 * Always fixed with 2 digits after the dot, preceded with a plus or minus sign.
 *
 * @param {number} delta the bundle size delta.
 * @return {string} formatted bundle size delta.
 */
function formatBundleSizeDelta(delta) {
  return 'Δ ' + (delta >= 0 ? '+' : '') + delta.toFixed(2) + 'KB';
}

module.exports = app => {
  /**
   * Get the GitHub Check object from the database.
   *
   * @param {string} headSha commit SHA of the head commit of a pull request.
   * @return {!Object} GitHub Check object.
   */
  async function getCheckFromDatabase(headSha) {
    const results = await db('checks')
        .select('head_sha', 'pull_request_id', 'installation_id', 'owner',
            'repo', 'check_run_id', 'delta')
        .where('head_sha', headSha);
    if (results.length > 0) {
      return results[0];
    } else {
      return null;
    }
  }

  /**
   * Try to report the bundle size of a pull request to the GitHub check.
   *
   * @param {!Object} check GitHub Check object.
   * @param {string} baseSha commit SHA of the base commit being compared to.
   * @param {number} bundleSize the total bundle size in KB.
   * @param {boolean} lastAttempt true if this is the last retry.
   * @return {boolean} true if succeeded; false otherwise.
   */
  async function tryReport(check, baseSha, bundleSize, lastAttempt = false) {
    const github = await app.auth(check.installation_id);
    const githubOptions = {
      owner: check.owner,
      repo: check.repo,
    };
    const updatedCheckOptions = Object.assign({
      check_run_id: check.check_run_id,
      name: 'ampproject/bundle-size',
      completed_at: new Date().toISOString(),
    }, githubOptions);

    try {
      const baseBundleSize = parseFloat(
          await getBuildArtifactsFile(github, baseSha));
      const bundleSizeDelta = bundleSize - baseBundleSize;
      const bundleSizeDeltaFormatted = formatBundleSizeDelta(bundleSizeDelta);

      await db('checks')
          .update({delta: bundleSizeDelta})
          .where({head_sha: check.head_sha});

      const requiresApproval =
          bundleSizeDelta > process.env['MAX_ALLOWED_INCREASE'];
      if (requiresApproval) {
        Object.assign(updatedCheckOptions, {
          conclusion: 'action_required',
          output: {
            title: `${bundleSizeDeltaFormatted} | approval required`,
            summary: 'This pull request has increased the bundle size ' +
              '(gzipped compressed size of `v0.js`) by ' +
              `${bundleSizeDeltaFormatted}. As part of an ongoing effort to ` +
              'reduce the bundle size, this change requires special approval.' +
              '\n' +
              'A member of the bundle-size group will be added automatically ' +
              'to review this PR. Only once the member approves this PR, ' +
              'can it be merged. If you do not receive a response from the ' +
              'group member, feel free to tag another person listed in the ' +
              'bundle-size [OWNERS](https://github.com/ampproject/' +
              'amphtml-build-artifacts/blob/master/bundle-size/OWNERS) file.',
          },
        });
      } else {
        Object.assign(updatedCheckOptions, {
          conclusion: 'success',
          output: {
            title: `${bundleSizeDeltaFormatted} | no approval necessary`,
            summary: 'This pull request does not increase the bundle size ' +
              '(gzipped compressed size of `v0.js`) by any significant ' +
              'amount, so no special approval is necessary. The bundle size ' +
              `change is ${bundleSizeDeltaFormatted}`,
          },
        });
      }
      await github.checks.update(updatedCheckOptions);

      if (requiresApproval) {
        await addOwnersReviewer(github,
            Object.assign({number: check.pull_request_id}, githubOptions));
      }

      return true;
    } catch (error) {
      const partialHeadSha = check.head_sha.substr(0, 7);
      const partialBaseSha = baseSha.substr(0, 7);
      app.log('ERROR: Failed to retrieve the bundle size of ' +
              `${partialHeadSha} (PR #${check.pull_request_id}) with branch ` +
              `point ${partialBaseSha} from GitHub: ${error}`);
      if (lastAttempt) {
        app.log('No more retries left. Reporting failure');
        Object.assign(updatedCheckOptions, {
          conclusion: 'action_required',
          output: {
            title: 'Failed to retrieve the bundle size of branch point ' +
                partialBaseSha,
            summary: 'The bundle size (gzipped compressed size of `v0.js`) ' +
              'of this pull request could not be determined because the base ' +
              'size (that is, the bundle size of the `master` commit that ' +
              'this pull request was compared against) was not found in the ' +
              '`https://github.com/ampproject/amphtml-build-artifacts` ' +
              'repository. This can happen due to failed or delayed Travis ' +
              'builds on said `master` commit.\n' +
              'A member of the bundle-size group will be added automatically ' +
              'to review this PR. Only once the member approves this PR, ' +
              'can it be merged. If you do not receive a response from the ' +
              'group member, feel free to tag another person listed in the ' +
              'bundle-size [OWNERS](https://github.com/ampproject/' +
              'amphtml-build-artifacts/blob/master/bundle-size/OWNERS) file.',
          },
        });
        await github.checks.update(updatedCheckOptions);
        await addOwnersReviewer(github,
            Object.assign({number: check.pull_request_id}, githubOptions));
      }
      return false;
    }
  }

  /**
   * Add an OWNERS reviewer to the pull request.
   *
   * Ignore errors as this is a non-critical action.
   *
   * @param {github} github an authenticated GitHub API object.
   * @param {!Object} pullRequest GitHub Pull Request object.
   */
  async function addOwnersReviewer(github, pullRequest) {
    try {
      const owners = await getOwners(github);
      const reviewersResponse = await github.pullRequests.listReviewRequests(
          pullRequest);
      const reviewers = new Set(
          reviewersResponse.data.users.map(user => user.login));
      if (owners.some(owner => reviewers.has(owner))) {
        app.log(`INFO: Pull request ${pullRequest.number} already has an ` +
                'OWNERS reviewer. Skipping...');
        return;
      }

      // Choose a random OWNERS user and add them as a reviewer to the pull
      // request.
      const newReviewer = owners[Math.floor(Math.random() * owners.length)];
      return await github.pullRequests.createReviewRequest(Object.assign({
        reviewers: [newReviewer],
      }, pullRequest));
    } catch (error) {
      app.log('ERROR: Failed to add a reviewer to pull request ' +
              `${pullRequest.number}. Skipping...`);
      app.log(`Error message: ${error}`);
    }
  }

  app.on(['pull_request.opened', 'pull_request.synchronize'], async context => {
    context.log(`Pull request ${context.payload.number} created/updated`);

    const headSha = context.payload.pull_request.head.sha;
    const params = context.repo({
      name: 'ampproject/bundle-size',
      head_sha: headSha,
      output: {
        title: 'Calculating new bundle size for this PR…',
        summary: 'The bundle size (gzipped compressed size of `v0.js`) ' +
          'of this pull request is being calculated on Travis. Look for the ' +
          'shard that contains "Bundle Size" in its title.',
      },
    });
    const check = await context.github.checks.create(params);

    const checkRunId = check.data.id;
    await db.transaction(trx => {
      return trx('checks')
          .first('head_sha')
          .where('head_sha', headSha)
          .then(existingRow => {
            if (existingRow === undefined) {
              return trx('checks').insert({
                head_sha: headSha,
                pull_request_id: context.payload.number,
                installation_id: context.payload.installation.id,
                owner: params.owner,
                repo: params.repo,
                check_run_id: checkRunId,
              });
            } else {
              return trx('checks')
                  .update({check_run_id: checkRunId})
                  .where({head_sha: headSha});
            }
          }).then(trx.commit).catch(trx.rollback);
    });
  });

  app.on('pull_request_review.submitted', async context => {
    const approver = context.payload.review.user.login;
    const owners = await getOwners(context.github);
    const pullRequestId = context.payload.pull_request.number;
    const headSha = context.payload.pull_request.head.sha;

    if (context.payload.review.state == 'approved' &&
        owners.includes(approver)) {
      context.log(`Pull request ${pullRequestId} approved by a bundle-size ` +
          'keeper');

      const check = await getCheckFromDatabase(headSha);
      if (!check) {
        context.log(`Check ID for pull request ${pullRequestId} with head ` +
            `SHA ${headSha} was not found in the database`);
        return;
      }

      let approvalMessagePrefix;
      if (check.delta === null) {
        approvalMessagePrefix = 'Δ ±?.??KB';
      } else {
        const bundleSizeDelta = parseFloat(check.delta);
        if (bundleSizeDelta <= process.env['MAX_ALLOWED_INCREASE']) {
          return;
        }
        approvalMessagePrefix = formatBundleSizeDelta(bundleSizeDelta);
      }

      await context.github.checks.update({
        owner: check.owner,
        repo: check.repo,
        check_run_id: check.check_run_id,
        conclusion: 'success',
        completed_at: new Date().toISOString(),
        output: {
          title: `${approvalMessagePrefix} | approved by @${approver}`,
          summary: 'The bundle size (gzipped compressed size of `v0.js`) ' +
            `of this pull request was approved by ${approver}`,
        },
      });
    }
  });

  const v0 = app.route('/v0');
  v0.use((request, response, next) => {
    request.app.set('trust proxy', true);
    if ('TRAVIS_IP_ADDRESSES' in process.env &&
        !process.env['TRAVIS_IP_ADDRESSES'].includes(request.ip)) {
      app.log(`Refused a request to ${request.originalUrl} from ${request.ip}`);
      response.status(403).end('You are not Travis!');
    } else {
      next();
    }
  });
  v0.use(require('express').json());

  v0.post('/commit/:headSha/skip', async (request, response) => {
    const {headSha} = request.params;
    app.log(`Marking SHA ${headSha} for skip`);

    const check = await getCheckFromDatabase(headSha);
    if (!check) {
      return response.status(404).end(`${headSha} not in database`);
    }
    const github = await app.auth(check.installation_id);
    await github.checks.update({
      owner: check.owner,
      repo: check.repo,
      check_run_id: check.check_run_id,
      conclusion: 'neutral',
      completed_at: new Date().toISOString(),
      output: {
        title: 'check skipped because PR contains no runtime changes',
        summary: 'An automated check has determined that the bundle size ' +
          '(gzipped compressed size of `v0.js`) could not be affected by ' +
          'the files that this pull request modifies, so this check was ' +
          'marked as skipped.',
      },
    });
    response.end();
  });

  v0.post('/commit/:headSha/report', async (request, response) => {
    if (!('baseSha' in request.body) ||
        typeof request.body.baseSha != 'string' ||
        !/^[0-9a-f]{40}$/.test(request.body.baseSha)) {
      return response.status(400).end(
          'POST request to /report must have commit SHA field "baseSha"');
    }
    if (!('bundleSize' in request.body) ||
        typeof request.body.bundleSize != 'number') {
      return response.status(400).end(
          'POST request to /report must have numeric field "bundleSize"');
    }
    const {headSha} = request.params;
    const {baseSha, bundleSize} = request.body;

    const check = await getCheckFromDatabase(headSha);
    if (!check) {
      return response.status(404).end(`${headSha} not in database`);
    }

    let reportSuccess = await tryReport(check, baseSha, bundleSize);
    if (reportSuccess) {
      response.end();
    } else {
      response.status(202).end();
      let retriesLeft = RETRY_TIMES - 1;
      do {
        app.log(`Will retry ${retriesLeft} more time(s) in ${RETRY_MILLIS} ms`);
        await sleep(RETRY_MILLIS);
        retriesLeft--;
        reportSuccess = await tryReport(
            check, baseSha, bundleSize, /* lastAttempt */ retriesLeft == 0);
      } while (retriesLeft > 0 && !reportSuccess);
    }
  });
};
<|endoftext|>