<commit_before>import React, { Component, PropTypes } from 'react';
import { compose, graphql } from 'react-apollo';
import camelCase from 'camelcase';
import {List, ListItem} from 'material-ui/List';
import FontIcon from 'material-ui/FontIcon';

import history from 'lib/history';

import formWrapper from 'lib/formWrapper';

import {
  validateString,
} from 'lib/validateComponentForms';

import Link from 'components/Link';
import ContactForm from 'components/ContactForm';

import { MeQuery } from 'schemas/queries';

import { ContactMutation } from 'schemas/mutations';

import {
  notify
} from 'actions/NotificationsActions';

import s from 'styles/Settings.scss';

const WrappedContactForm = formWrapper(ContactForm);

class Contact extends Component {

  static PropTypes = {
  }

  constructor(props) {
    super(props);

    const initialState = {
      formData: {
        subject: '',
        body: '',
      },
      saving: false
    }

    this.state = Object.assign({}, initialState);
  }

  defaultErrorText = {
    subjectErrorText: null,
    bodyErrorText: null,
  }

  formSubmit = async (data) => {

    const formData = Object.assign({}, data);

    this.setState({ saving: true });

    try {

      const results = await this.props.contactMutation({
        variables: {
          data: formData
        },
      });

      this.props.dispatch(notify('Message Sent!'));
      this.setState({ saving: false });
    } catch (e) {
      console.error(e);
      this.props.dispatch(notify('There was an error with your request. Please reload the page or contact help@uprise.org for support.'));
      this.setState({ saving: false });
    }
  }

  render() {

    if (this.props.user) {
      const { state, formSubmit, defaultErrorText } = this;
      const { user, ...props } = this.props;
      const { formData, saving } = state;

      const validators = [
        (component) => validateString(component, 'subject', 'subjectErrorText', 'Please enter a subject'),
        (component) => validateString(component, 'body', 'bodyErrorText', 'Please enter a message'),
      ];

      return (

          <Link to={'/settings'}>

              <FontIcon
                className={["material-icons", s.backArrow].join(' ')}
              >arrow_back</FontIcon>
              Settings

          </Link>

Contact

          <WrappedContactForm
            initialState={formData}
            initialErrors={defaultErrorText}
            validators={validators}
            submit={formSubmit}
            saving={saving}
            submitText="Send"
            user={user}
          />

      );
    } else {
      return null;
    }
  }
}

export default compose(
  graphql(MeQuery, {
    options: (ownProps) => ({
      fetchPolicy: 'cache-and-network',
    }),
    props: ({ data }) => ({
      user: data.me,
      graphqlLoading: data.loading
    })
  }),
  graphql(ContactMutation, { name: 'contactMutation' })
)(Contact);
<commit_msg>Navigate backwards after successful Contact message sent.<commit_after>import React, { Component, PropTypes } from 'react';
import { compose, graphql } from 'react-apollo';
import camelCase from 'camelcase';
import {List, ListItem} from 'material-ui/List';
import FontIcon from 'material-ui/FontIcon';

import history from 'lib/history';

import formWrapper from 'lib/formWrapper';

import {
  validateString,
} from 'lib/validateComponentForms';

import Link from 'components/Link';
import ContactForm from 'components/ContactForm';

import { MeQuery } from 'schemas/queries';

import { ContactMutation } from 'schemas/mutations';

import {
  notify
} from 'actions/NotificationsActions';

import s from 'styles/Settings.scss';

const WrappedContactForm = formWrapper(ContactForm);

class Contact extends Component {

  static PropTypes = {
  }

  constructor(props) {
    super(props);

    const initialState = {
      formData: {
        subject: '',
        body: '',
      },
      saving: false
    }

    this.state = Object.assign({}, initialState);
  }

  defaultErrorText = {
    subjectErrorText: null,
    bodyErrorText: null,
  }

  formSubmit = async (data) => {

    const formData = Object.assign({}, data);

    this.setState({ saving: true });

    try {

      const results = await this.props.contactMutation({
        variables: {
          data: formData
        },
      });

      this.props.dispatch(notify('Message Sent!'));
      this.setState({ saving: false });

      setTimeout( () => {
        history.goBack();
      }, 500);

    } catch (e) {
      console.error(e);
      this.props.dispatch(notify('There was an error with your request. Please reload the page or contact help@uprise.org for support.'));
      this.setState({ saving: false });
    }
  }

  render() {

    if (this.props.user) {
      const { state, formSubmit, defaultErrorText } = this;
      const { user, ...props } = this.props;
      const { formData, saving } = state;

      const validators = [
        (component) => validateString(component, 'subject', 'subjectErrorText', 'Please enter a subject'),
        (component) => validateString(component, 'body', 'bodyErrorText', 'Please enter a message'),
      ];

      return (

          <Link to={'/settings'}>

              <FontIcon
                className={["material-icons", s.backArrow].join(' ')}
              >arrow_back</FontIcon>
              Settings

          </Link>

Contact

          <WrappedContactForm
            initialState={formData}
            initialErrors={defaultErrorText}
            validators={validators}
            submit={formSubmit}
            saving={saving}
            submitText="Send"
            user={user}
          />

      );
    } else {
      return null;
    }
  }
}

export default compose(
  graphql(MeQuery, {
    options: (ownProps) => ({
      fetchPolicy: 'cache-and-network',
    }),
    props: ({ data }) => ({
      user: data.me,
      graphqlLoading: data.loading
    })
  }),
  graphql(ContactMutation, { name: 'contactMutation' })
)(Contact);
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import Relay from 'react-relay/classic';

import Spinner from '../../shared/Spinner';

import AuditLogEventSection from './eventSection';
import AuditLogSubjectSection from './subjectSection';
import AuditLogActorSection from './actorSection';
import AuditLogContextSection from './contextSection';

class AuditLogDrawer extends React.PureComponent {
  static propTypes = {
    auditEvent: PropTypes.object.isRequired,
    loading: PropTypes.bool.isRequired,
    relay: PropTypes.object.isRequired
  };

  render() {
    if (this.props.loading) {
      return (

          <Spinner style={{ margin: 9.5 }} />

      );
    }

    return (

        <AuditLogEventSection
          auditEvent={this.props.auditEvent}
          hasExpanded={this.props.relay.variables.hasExpanded}
        />
        <AuditLogSubjectSection
          auditEvent={this.props.auditEvent}
        />
        <AuditLogActorSection
          auditEvent={this.props.auditEvent}
        />
        <AuditLogContextSection
          auditEvent={this.props.auditEvent}
        />

    );
  }
}

export default Relay.createContainer(AuditLogDrawer, {
  initialVariables: {
    hasExpanded: false
  },

  fragments: {
    auditEvent: ({ hasExpanded }) => Relay.QL`
      fragment on AuditEvent {
        ${AuditLogEventSection.getFragment('auditEvent', { hasExpanded })}
        ${AuditLogSubjectSection.getFragment('auditEvent')}
        ${AuditLogActorSection.getFragment('auditEvent')}
        ${AuditLogContextSection.getFragment('auditEvent')}
      }
    `
  }
});
<commit_msg>Hotfix: center spinner on audit log<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import Relay from 'react-relay/classic';

import Spinner from '../../shared/Spinner';

import AuditLogEventSection from './eventSection';
import AuditLogSubjectSection from './subjectSection';
import AuditLogActorSection from './actorSection';
import AuditLogContextSection from './contextSection';

class AuditLogDrawer extends React.PureComponent {
  static propTypes = {
    auditEvent: PropTypes.object.isRequired,
    loading: PropTypes.bool.isRequired,
    relay: PropTypes.object.isRequired
  };

  render() {
    if (this.props.loading) {
      return (

          <Spinner style={{ margin: 9.5 }} />

      );
    }

    return (

        <AuditLogEventSection
          auditEvent={this.props.auditEvent}
          hasExpanded={this.props.relay.variables.hasExpanded}
        />
        <AuditLogSubjectSection
          auditEvent={this.props.auditEvent}
        />
        <AuditLogActorSection
          auditEvent={this.props.auditEvent}
        />
        <AuditLogContextSection
          auditEvent={this.props.auditEvent}
        />

    );
  }
}

export default Relay.createContainer(AuditLogDrawer, {
  initialVariables: {
    hasExpanded: false
  },

  fragments: {
    auditEvent: ({ hasExpanded }) => Relay.QL`
      fragment on AuditEvent {
        ${AuditLogEventSection.getFragment('auditEvent', { hasExpanded })}
        ${AuditLogSubjectSection.getFragment('auditEvent')}
        ${AuditLogActorSection.getFragment('auditEvent')}
        ${AuditLogContextSection.getFragment('auditEvent')}
      }
    `
  }
});
<|endoftext|>


<commit_before>import Ember from 'ember';
import CpPanelBodyComponent from 'ember-collapsible-panel/components/cp-panel-body';
import PreprintFormFieldMixin from '../mixins/preprint-form-field';

export default CpPanelBodyComponent.extend(PreprintFormFieldMixin, {
    filter: [{}, {}, {}],
    filteredPath: Ember.computed('path', 'filter', 'filter.@each.value', function() {
        return this.get('path').slice(0, 2).map((path, i) => {
            if (path.children && this.get(`filter.${i + 1}.value`)) {
                return {
                    name: path.name,
                    children: path.children.filter(child =>
                        this.get(`filter.${i + 1}.value`).indexOf(child.name || child) !== -1)
                };
            }
            return path;
        });
    }),
    sortedTaxonomies: Ember.computed('taxonomies', 'filter', 'filter.0.value', function() {
        return [{
            name: 'a',
            children: [{
                name: 'b',
                children: ['c', 'd', 'e']
            }, {
                name: 'f',
                children: ['g']
            }],
        }, {
            name: 'h',
            children: [{
                name: 'i',
                children: ['j', 'k']
            }]
        }, {
            name: 'l'
        }].filter(taxonomy =>
            !this.get('filter.0.value') || taxonomy.name.indexOf(this.get('filter.0.value')) !== -1
        );
    }),
    path: [],
    selected: new Ember.Object(),
    // sortedSelection: Ember.computed('selected', function() {
    //     const selected = this.get('selected');
    //     const taxonomies = Object.keys(selected);
    //     const temp = taxonomies
    //         .map(taxonomy => selected.get(taxonomy))
    //         .filter(categories => categories);
    //     const categories = temp
    //         .reduce((prev, cur) => prev.concat(Object.keys(cur)), []);
    //     const subjects = temp
    //         .map(categories => Object.keys(categories)
    //             .map(category => categories.get(category))
    //             .filter(subjects => subjects)
    //             .reduce((prev, cur) => prev.concat(Object.keys(cur)), []))
    //         .reduce((prev, cur) => prev.concat(cur));
    //     return {
    //         taxonomies: taxonomies,
    //         categories: categories,
    //         subjects: subjects
    //     };
    // }),
    sortedSelection: Ember.computed('selected', function() {
        const sorted = [];
        const selected = this.get('selected');
        const flatten = ([obj, name = []]) => {
            const keys = Object.keys(obj);
            if (keys.length === 0) {
                return name.length !== 0 && sorted.pushObject(name);
            } else {
                return keys.sort().map(key => [obj.get(key), [...name, key]]).forEach(flatten);
            }
        };
        flatten([selected]);
        return sorted;
    }),
    valid: Ember.computed('selected', function() {
        return Object.keys((this.get('selected'))).length !== 0;
    }),
    actions: {
        delete(key) {
            this.set(key, null);
            // Handle keys with spaces
            eval(`delete this['${key.replace(/\./g, "']['")}']`);
        },
        deselect([...args]) {
            this.send('delete', `selected.${args.filter(arg => Ember.typeOf(arg) === 'string').join('.')}`);
            this.notifyPropertyChange('selected');
            this.rerender();
        },
        select(...args) {
            const process = (prev, cur, i) => {
                if (!this.get(`selected.${prev}`)) {
                    // Create necessary parent objects and newly selected object
                    this.set(`selected.${prev}`, new Ember.Object());
                } else if (i === args.length && this.get('path').every((e, i) => e === args[i])
                    && Object.keys(this.get(`selected.${prev}`)).length === 0) {
                    // Deselecting a subject: if subject is last item in args,
                    // its children are showing, and no children are selected
                    this.send('delete', `selected.${prev}`);
                    args.popObject();
                }
                return `${prev}.${cur}`;
            };
            // Process past length of array
            process(args.map(arg => arg.name || arg).reduce(process), '', args.length);
            this.set('path', args);
            this.notifyPropertyChange('selected');
            this.rerender();
        }
    }
});
<commit_msg>Fix selection edge case<commit_after>import Ember from 'ember';
import CpPanelBodyComponent from 'ember-collapsible-panel/components/cp-panel-body';
import PreprintFormFieldMixin from '../mixins/preprint-form-field';

export default CpPanelBodyComponent.extend(PreprintFormFieldMixin, {
    filter: [{}, {}, {}],
    filteredPath: Ember.computed('path', 'filter', 'filter.@each.value', function() {
        return this.get('path').slice(0, 2).map((path, i) => {
            if (path.children && this.get(`filter.${i + 1}.value`)) {
                return {
                    name: path.name,
                    children: path.children.filter(child =>
                        this.get(`filter.${i + 1}.value`).indexOf(child.name || child) !== -1)
                };
            }
            return path;
        });
    }),
    sortedTaxonomies: Ember.computed('taxonomies', 'filter', 'filter.0.value', function() {
        return [{
            name: 'a',
            children: [{
                name: 'b',
                children: ['c', 'd', 'e']
            }, {
                name: 'f',
                children: ['g']
            }],
        }, {
            name: 'h',
            children: [{
                name: 'i',
                children: ['j', 'k']
            }]
        }, {
            name: 'l'
        }].filter(taxonomy =>
            !this.get('filter.0.value') || taxonomy.name.indexOf(this.get('filter.0.value')) !== -1
        );
    }),
    path: [],
    selected: new Ember.Object(),
    sortedSelection: Ember.computed('selected', function() {
        const sorted = [];
        const selected = this.get('selected');
        const flatten = ([obj, name = []]) => {
            const keys = Object.keys(obj);
            if (keys.length === 0) {
                return name.length !== 0 && sorted.pushObject(name);
            } else {
                return keys.sort().map(key => [obj.get(key), [...name, key]]).forEach(flatten);
            }
        };
        flatten([selected]);
        return sorted;
    }),
    valid: Ember.computed('selected', function() {
        return Object.keys((this.get('selected'))).length !== 0;
    }),
    actions: {
        delete(key) {
            this.set(key, null);
            // Handle keys with spaces
            eval(`delete this['${key.replace(/\./g, "']['")}']`);
        },
        deselect([...args]) {
            this.send('delete', `selected.${args.filter(arg => Ember.typeOf(arg) === 'string').join('.')}`);
            this.notifyPropertyChange('selected');
            this.rerender();
        },
        select(...args) {
            const process = (prev, cur, i) => {
                if (!this.get(`selected.${prev}`)) {
                    // Create necessary parent objects and newly selected object
                    this.set(`selected.${prev}`, new Ember.Object());
                } else if (i === args.length && args.length === this.get('path').length &&
                    this.get('path').every((e, i) => e.name === args[i].name) &&
                    Object.keys(this.get(`selected.${prev}`)).length === 0) {
                    // Deselecting a subject: if subject is last item in args,
                    // its children are showing, and no children are selected
                    this.send('delete', `selected.${prev}`);
                    args.popObject();
                }
                return `${prev}.${cur}`;
            };
            // Process past length of array
            process(args.map(arg => arg.name || arg).reduce(process), '', args.length);
            this.set('path', args);
            this.notifyPropertyChange('selected');
            this.rerender();
        }
    }
});
<|endoftext|>


<commit_before>'use strict';

angular
  .module('lsSearchBar', [
    'listExtractionFactory',
    'ui.bootstrap'
  ])

  .controller('lsSearchBarCtrl', ['$scope', '$timeout', 'ListExtractionFactory', function ($scope, $timeout, ListExtractionFactory) {
    //TODO: Refactor - Extract status UI out of lsSearchBarCtrl
    var NO_TABLE_ALERT_MSG = 'No table found on target page. If there IS a table, then the table implementation is not supported. This app finds tables by the HTML <table> element.';
    var PROCESSING_MSG = 'Processing...';
    var UNRESPONSIVE_MSG = 'Target site is unresponsive. Try again or try a different site.';
    var EMPTY_URL_MSG = 'Empty URL';

    this.srcUrl = '';
    this.alert = {
      show: false,
      type: '',
      msg: '',

      reset: function () {
        this.show = false;
      },

      warning: function (msg) {
        this.show = true;
        this.type = '';
        this.msg = msg;
      },

      error: function (msg) {
        this.show = true;
        this.type = 'danger';
        this.msg = msg;
      }
    };

    var _alert = this.alert;

    ListExtractionFactory.listen(function (event, data) {
      if (data === 0) {
        $timeout(function() {
          _alert.warning(NO_TABLE_ALERT_MSG);
        });
      }
    });

    this.getTables = function () {
      // url empty
      if (this.srcUrl === '') {
        _alert.error(EMPTY_URL_MSG);
        return;
      }

      /* HACK: url missing protocol
       * Potential problem: protocol could be https
       */
      var regex = /^http/;
      if ( !regex.test(this.srcUrl) ) {
        this.srcUrl = 'http://' + this.srcUrl;
      }

      _alert.warning(PROCESSING_MSG);

      ListExtractionFactory.extract(this.srcUrl)
        .success(function() {
            _alert.reset();
        })
        .error(function() {
            _alert.error(UNRESPONSIVE_MSG);
        });
    };

    this.getPresetSite = function(url) {
      this.srcUrl = url;
      this.getTables();
    };

  }])

  .directive('lsSearchBar', function () {
    return {
      scope: {},
      templateUrl: 'components/search_bar/ls-search-bar.html',
      controller: 'lsSearchBarCtrl',
      controllerAs: 'ctrl'
    };
  });
<commit_msg>Add logic to determine when HTTP GET failure occurs before timeout; Add url validity check; Remove empty url check<commit_after>'use strict';

angular
  .module('lsSearchBar', [
    'listExtractionFactory',
    'ui.bootstrap'
  ])

  .controller('lsSearchBarCtrl', ['$scope', '$timeout', 'ListExtractionFactory', function ($scope, $timeout, ListExtractionFactory) {
    //TODO: Refactor - Extract status UI out of lsSearchBarCtrl
    var NO_TABLE_ALERT_MSG = 'No table found on target page. If there IS a table, then the table implementation is not supported. This app finds tables by the HTML <table> element.';
    var PROCESSING_MSG = 'Processing...';
    var UNRESPONSIVE_MSG = 'Target site is unresponsive. Try a different site.';
    var FAST_FAIL_MSG = 'There\'s a slight hiccup with the request. It should work within 3 tries. (The issue is being resolved)';
    var NOT_HTTP_MSG = 'Invalid URL. Note: The url should be preceded with http(s)://';

    this.srcUrl = '';
    this.alert = {
      show: false,
      type: '',
      msg: '',

      reset: function () {
        this.show = false;
      },

      warning: function (msg) {
        this.show = true;
        this.type = '';
        this.msg = msg;
      },

      error: function (msg) {
        this.show = true;
        this.type = 'danger';
        this.msg = msg;
      }
    };

    var _alert = this.alert;

    ListExtractionFactory.listen(function (event, data) {
      if (data === 0) {
        $timeout(function() {
          _alert.warning(NO_TABLE_ALERT_MSG);
        });
      }
    });

    this.getTables = function () {
      // Check url validity. Only http(s) protocol is allowed
      var regex = /^https?:\/\/.+/;
      if ( !regex.test(this.srcUrl) ) {
        _alert.error(NOT_HTTP_MSG);
        return;
      }

      /* Hack: get around intermittent failure when the http request fails immediately (possibly caused by whatever origin)
       * (cannot differentiate between this and timeout because both callback arguments are empty)
       * Cancel the timer in the extract error callback. If too soon, throw retry message.
       */
      var timer = $timeout(function(){}, 1000);

      _alert.warning(PROCESSING_MSG);
      ListExtractionFactory.extract(this.srcUrl)
        .success(function() {
            _alert.reset();
        })
        .error(function() {
          // HACK: Part of the Hack described above
          if ($timeout.cancel(timer)) {
            _alert.error(FAST_FAIL_MSG);
          } else {
            _alert.error(UNRESPONSIVE_MSG);
          }
        });
    };

    this.getPresetSite = function(url) {
      this.srcUrl = url;
      this.getTables();
    };

  }])

  .directive('lsSearchBar', function () {
    return {
      scope: {},
      templateUrl: 'components/search_bar/ls-search-bar.html',
      controller: 'lsSearchBarCtrl',
      controllerAs: 'ctrl'
    };
  });
<|endoftext|>


<commit_before>'use strict';

var mongoose = require('mongoose'),
	errorHandler = require('./errors'),
	Match = mongoose.model('Match'),
	Ticket = mongoose.model('Ticket');
//	async = require('async');

exports.requestTicket = function(req, res){
	var newTicket = new Ticket(req.body);
	newTicket.save(function(err){
				if(err){
					console.log('Could not create new ticket');
					return res.status(500).send({
							message: 'Error ocurred while creating ticket'
						});
				}

	});
	return res.status(201).send(newTicket._id);
};

exports.requestMatch = function(req, res, next){
	Ticket.findById(req.body.ticketId, function(err, ticket){
		if(err)
		return res.status(500).send({
							message: 'Error ocurred'
						});
		if(ticket){
			if(ticket.matchId){

				Match.findById(ticket.matchId, function(err,match){
					match.status = 'established';
					match.save(function(err){
					if(err)
						return res.status(500).send({
							message: 'Error ocurred while updating match status to established'
						});

					});
					Ticket.findByIdAndRemove(ticket.id, {}, function(err) {
    					if(err)
							return res.status(500).send({
								message: 'Error ocurred while removing ticket'
							});
					});

					return res.send({matchId: match.id, players: match.players, player: 0, nextTurn:0}); //player: 0 = first player
				});

			}else{
				Ticket.findOne({}, function (err, oponent){
					console.log(oponent);
					if(err)
						return res.status(500).send({
							message: 'Could not processed requet'
						});
					if (oponent && ticket.id !== oponent.id) {
						var newMatch = new Match({
								players:
									[{ name: oponent.name, elo: oponent.elo, ticket: oponent.id, submitTurn: true},
						 			{ name: ticket.name, elo: ticket.elo, ticket: ticket.id, submitTurn: false}],
								match_info: [{player1: oponent.id, player2: ticket.id},
											 {turn: oponent.id}],//-->Empieza el player1
								init_date: new Date(),
								turns: [],
								status: 'not established'
							});
	    				newMatch.save(function(err){
						if(err){
							console.log('Could not create new match');
							return res.status(500).send({
										message: 'Error ocurred while creating match'
								});
						}

	  	    			});
	  	    			oponent.match = newMatch.id;
	  	    			oponent.save(function(err){
	  	    				return res.status(500).send({
										message: 'Error ocurred while updating match in oponent ticket'
								});

	  	    			});
    					Ticket.findByIdAndRemove(ticket.id, {}, function(err) {
    					if(err)
							return res.status(500).send({
								message: 'Error ocurred while removing ticket'
							});
						});
        				return res.send({matchId: newMatch.id, players: newMatch.players,
        								player: 1,  nextTurn:1}); //player: 1 = second player

    				}else{
      					return res.send('Could not find adversary. Please wait for an oponent...');
					}

		    	});
			}
		}else{
			return res.status(400).send({
							message: 'Ticket '+req.body.ticketId+ 'does not exist'
						});
		}
	});

};

exports.waitTurn = function(req, res, next){
	var params = req.body;
	var matchId = req.body.matchId;
	var nextTurn = params.nextTurn;
	var player = params.player;
	var players = params.players;
	Match.findById(matchId, function(err, match){

		if(err)
			return res.status(500).send({
										message: 'Error ocurred while looking for match with id = '+matchId
								});
		if(match){
			if(match.players[player].submitTurn === false){
				var last_turn = match.turns.length -1;
				if(last_turn === nextTurn){
					match.players[player].submitTurn = true;
			   		//submitTurn: true--> Le toca el turno
					match.save(function(err){
						if(err)
							return res.status(500).send({
										message: 'Error ocurred while updating match with id = '+match.id
								});
					});
					//return res.send({matchId: matchId, players: players, player: player, nextTurn: nextTurn});
					return res.send('It is your turn, submit turn');
				}else{
					return res.send('Waiting ...');
								//submitTurn: false--> Sigue en espera
				}
			}else{
				return res.send('It is your turn, submit turn');
			}

		}else{
			return  res.status(400).send({
										message: 'ERROR: There is no match with id = '+matchId
								});

		}

	});

};

exports.submitTurn = function(req, res){
	var params = req.body;
	var matchId = params.matchId;
	var turn = params.turn;
	var player = params.player;
	var user = params.players[player].ticket;
	var nextTurn = params.nextTurn;
	Match.findById(matchId, function(err, match){

		if(err)
			return res.status(500).send({
										message: 'Error ocurred while looking for match with id = '+matchId
								});
		if(match){
				if(match.players[player].submitTurn === true){

					//if(parseInt(match.match_info[1].turn) === parseInt(user)){ //->para hacer pruebas con tickets numéricos
					if(match.match_info[1].turn === user){
						//var last_turn = match.turns.length - 1;
						var oponent = 1 - player;
						match.turns[nextTurn] = turn;
						match.match_info[1].turn = match.players[oponent].ticket;
						match.players[params.player].submitTurn = false;
						match.save(function(err){
							if(err)
							return res.status(500).send({
										message: 'Error ocurred while submiting saving new turn'
								});
						});
						// submitTurn: false:1 --> Ahora le tocará esperar por el próximo turno
						var newTurn = match.turns.length;
						res.send({matchId: matchId, players: params.players, player: player,  nextTurn: newTurn});
					}else{
						return res.status(400).send({
										message: 'It is not your turn'
								});
					}

				}else{
					return res.status(400).send({
										message: 'Error: It is not your turn'
								});

				}
		}else{
			return  res.status(400).send({
										message: 'ERROR: There is no match with id = '+matchId
								});
		}

	});

};

exports.getMatchStatus = function(req, res){
	var matchId = req.body.matchId;
	Match.findById(matchId, 'status' ,function(err, match_status){
		if(err)
			return res.status(500).send({
							message: 'Error ocurred while creating match'
			});

		res.send(match_status);
	});
};

exports.setMatchStatus = function(req, res){
	var matchId = req.body.matchId;
	Match.findById(matchId, function (err, match) {
    	if (err)
    		return res.status(500).send({
							message: 'Error ocurred while looking for match to update'
			});

    	if(match){
    		match.status = req.body.status;

       		match.save(function (err) {
    			if (err)
    			return res.status(500).send({
							message: 'Error ocurred while updating match status'
				});

    			res.send(match);
    		});

    	}else{
    		res.send('ERROR: There is no Match with id = '+ matchId);
    	}

    });
};
<commit_msg>Modificado requestMatch: no se puede emparejar con uno mismo, no se puede emparejar con un jugador ya emparejado, arreglados algunos problemas de sincronismo<commit_after>'use strict';

var mongoose = require('mongoose'),
	errorHandler = require('./errors'),
	Match = mongoose.model('Match'),
	Ticket = mongoose.model('Ticket');
//	async = require('async');

exports.requestTicket = function(req, res){
	var newTicket = new Ticket(req.body);
	newTicket.save(function(err){
				if(err){
					console.log('Could not create new ticket');
					return res.status(500).send({
							message: 'Error ocurred while creating ticket'
						});
				}
		return res.status(201).send(newTicket._id);
	});
};

exports.requestMatch = function(req, res, next){
	Ticket.findById(req.body.ticketId, function(err, ticket){
		if(err)
		return res.status(500).send({
							message: 'Error retrieving ticket'
						});
		if(ticket){
			if(ticket.matchId){

				Match.findById(ticket.matchId, function(err,match){
					match.status = 'established';
					match.save(function(err){
					if(err)
						return res.status(500).send({
							message: 'Error ocurred while updating match status to established'
						});

					});
					Ticket.findByIdAndRemove(ticket.id, {}, function(err) {
    					if(err)
							return res.status(500).send({
								message: 'Error ocurred while removing ticket'
							});
					});
					return res.send({matchId: match.id, players: match.players, player: 0, nextTurn:0}); //player: 0 = first player
				});

			}else{
				Ticket.findOne({_id : {'$ne': ticket.id}, matchId: null}, function (err, oponent){
					if(err)
						return res.status(500).send({
							message: 'Could not processed requet'
						});
					if (oponent) {
						var newMatch = new Match({
								players:
									[{ name: oponent.name, elo: oponent.elo, ticket: oponent.id, submitTurn: true},
						 			{ name: ticket.name, elo: ticket.elo, ticket: ticket.id, submitTurn: false}],
								match_info: [{player1: oponent.id, player2: ticket.id},
											 {turn: oponent.id}],//-->Empieza el player1
								init_date: new Date(),
								turns: [],
								status: 'not established'
							});
	    				newMatch.save(function(err){
							if(err){
								console.log('Could not create new match');
								return res.status(500).send({
											message: 'Error ocurred while creating match'
									});
							}

		  	    			oponent.matchId = newMatch.id;
		  	    			oponent.save(function(err){
		  	    				if(err){
			  	    				return res.status(500).send({
												message: 'Error ocurred while updating match in oponent ticket  '+err
										});
								}
	    			    		Ticket.findByIdAndRemove(ticket.id, {}, function(err) {
    							if(err)
									return res.status(500).send({
										message: 'Error ocurred while removing ticket'
									});
  	    						return res.send({matchId: newMatch.id, players: newMatch.players,
   								player: 1,  nextTurn:1}); //player: 1 = second player
								});

		  	    			});

						});

    				}else{
      					return res.send('Could not find adversary. Please wait for an oponent...');
					}

		    	});
			}
		}else{
			return res.status(400).send({
							message: 'Ticket '+req.body.ticketId+ 'does not exist'
			});
		}
	});
};

exports.waitTurn = function(req, res, next){
	var params = req.body;
	var matchId = req.body.matchId;
	var nextTurn = params.nextTurn;
	var player = params.player;
	var players = params.players;
	Match.findById(matchId, function(err, match){

		if(err)
			return res.status(500).send({
										message: 'Error ocurred while looking for match with id = '+matchId
								});
		if(match){
			if(match.players[player].submitTurn === false){
				var last_turn = match.turns.length -1;
				if(last_turn === nextTurn){
					match.players[player].submitTurn = true;
			   		//submitTurn: true--> Le toca el turno
					match.save(function(err){
						if(err)
							return res.status(500).send({
										message: 'Error ocurred while updating match with id = '+match.id
								});
					});
					//return res.send({matchId: matchId, players: players, player: player, nextTurn: nextTurn});
					return res.send('It is your turn, submit turn');
				}else{
					return res.send('Waiting ...');
								//submitTurn: false--> Sigue en espera
				}
			}else{
				return res.send('It is your turn, submit turn');
			}

		}else{
			return  res.status(400).send({
										message: 'ERROR: There is no match with id = '+matchId
								});

		}

	});

};

exports.submitTurn = function(req, res){
	var params = req.body;
	var matchId = params.matchId;
	var turn = params.turn;
	var player = params.player;
	var user = params.players[player].ticket;
	var nextTurn = params.nextTurn;
	Match.findById(matchId, function(err, match){

		if(err)
			return res.status(500).send({
										message: 'Error ocurred while looking for match with id = '+matchId
								});
		if(match){
				if(match.players[player].submitTurn === true){

					//if(parseInt(match.match_info[1].turn) === parseInt(user)){ //->para hacer pruebas con tickets numéricos
					if(match.match_info[1].turn === user){
						//var last_turn = match.turns.length - 1;
						var oponent = 1 - player;
						match.turns[nextTurn] = turn;
						match.match_info[1].turn = match.players[oponent].ticket;
						match.players[params.player].submitTurn = false;
						match.save(function(err){
							if(err)
							return res.status(500).send({
										message: 'Error ocurred while submiting saving new turn'
								});
						});
						// submitTurn: false:1 --> Ahora le tocará esperar por el próximo turno
						var newTurn = match.turns.length;
						res.send({matchId: matchId, players: params.players, player: player,  nextTurn: newTurn});
					}else{
						return res.status(400).send({
										message: 'It is not your turn'
								});
					}

				}else{
					return res.status(400).send({
										message: 'Error: It is not your turn'
								});

				}
		}else{
			return  res.status(400).send({
										message: 'ERROR: There is no match with id = '+matchId
								});
		}

	});

};

exports.getMatchStatus = function(req, res){
	var matchId = req.body.matchId;
	Match.findById(matchId, 'status' ,function(err, match_status){
		if(err)
			return res.status(500).send({
							message: 'Error ocurred while creating match'
			});

		res.send(match_status);
	});
};

exports.setMatchStatus = function(req, res){
	var matchId = req.body.matchId;
	Match.findById(matchId, function (err, match) {
    	if (err)
    		return res.status(500).send({
							message: 'Error ocurred while looking for match to update'
			});

    	if(match){
    		match.status = req.body.status;

       		match.save(function (err) {
    			if (err)
    			return res.status(500).send({
							message: 'Error ocurred while updating match status'
				});

    			res.send(match);
    		});

    	}else{
    		res.send('ERROR: There is no Match with id = '+ matchId);
    	}

    });
};
<|endoftext|>


<commit_before>let selectedAssignees = [];

function addHeaderOptionsIcon() {
  const headerButtons = $('div.project-header > div.d-table.mt-1.float-right.f6');
  headerButtons.prepend(Handlebars.templates['header-options-icon']());
}

function addProjectsOptionsPane() {
  const panes = $('div.project-pane');
  const panesParent = panes.parent();
  const isFullscreen = window.location.search.match(/fullscreen=true/);

  panesParent.append(Handlebars.templates['projects-options-pane']({ isFullscreen }));
}

function addButtonsEventListeners() {
  $('button.gh-projects-options-toggle').on('click', toggleProjectOptionsPane);
  $('#gh-projects-options-assignees-button').on('click', updateAssigneesList);
  $('#gh-projects-options-add-column-toggle').on('click', toggleAddColumnEvent);
  $('#gh-projects-options-project-details-toggle').on('click', toggleProjectDetailsEvent);
  $('#gh-projects-options-project-settings-toggle').on('click', toggleProjectSettingsEvent);
}

function toggleProjectOptionsPane() {
  const pane = $('div.project-pane.gh-projects-options-pane');
  if (pane.hasClass('d-none')) {
    pane.removeClass('d-none');

    openPane();
  } else {
    pane.addClass('d-none');
  }
}

function addAssigneesFilter() {
  const headerButtons = $('div.project-header > div.d-table.mt-1.float-right.f6');

  headerButtons.prepend(Handlebars.templates['assignees-filter']());
}

function updateAssigneesList() {
  const assigneesList = $('#gh-projects-options-assignees-list');
  const assignees = getAssigneesList();
  const template = Handlebars.templates['assignees-list'];

  assigneesList.html(template({ assignees }));
  $('.gh-projects-options-assignees-toggle').on('click', toggleAssigneeEvent);
}

function toggleAssigneeEvent(e) {
  const parent = $(e.target).parents('a.select-menu-item');
  const assignee = parent.length ? parent.attr('id') : $(e.target).attr('id');
  const cards = $('.issue-card');
  const index = selectedAssignees.indexOf(assignee);

  if (index === -1) {
    selectedAssignees.push(assignee);
  } else {
    selectedAssignees.splice(index, 1);
  }

  cards.each((index, card) => {
    if (!selectedAssignees.length) {
      $(card).show();
    } else {
      const cardAssignees = $(card).find('img.avatar');
      let firstAssignee = cardAssignees.length ? cardAssignees[0].alt.replace('@', '') : '#nobody';

      if (selectedAssignees.includes(firstAssignee)) {
        $(card).show();
      } else {
        $(card).hide();
      }
    }
  });

  updateAssigneesList();
  e.stopPropagation();
}

function getRepositoriesList() {
  const repositories = new Set();
  const cardsDesc = $('.issue-card').find('small');

  cardsDesc.each((index, cardDesc) => {
    const cardText = cardDesc.innerText.trim();
    const matchRepo = cardText.match(/.*\/(.*)#\d+ .*/);
    if (matchRepo && matchRepo.length > 1) {
      repositories.add(matchRepo[1]);
    }
  });

  return repositories;
}

function getAssigneesList() {
  const assignees = {};
  const assigneesEls = $('.project-columns img.avatar');

  assigneesEls.each((index, assignee) => {
    const username = assignee.alt.substring(1);
    const avatar = assignee.src;
    const isSelected = selectedAssignees.includes(username);

    if (assignees[username]) {
      assignees[username].count += 1;
    } else {
      assignees[username] = { avatar, username, isSelected, count: 1 };
    }
  });

  // Add the fake assignee "nobody"
  const nobodyUsername = '#nobody';
  assignees[nobodyUsername] = {
    username: nobodyUsername,
    isSelected: selectedAssignees.includes(nobodyUsername),
    isNobody: true
  };

  return Object.keys(assignees).map(x => assignees[x]).sort((a, b) => a.username.toLowerCase() > b.username.toLowerCase());
}

function openPane() {
  // Hide repositories
  const repositories = Array.from(getRepositoriesList());

  $('#gh-projects-options-repolist').html(Handlebars.templates['repositories-checkboxes']({ repositories }));
  $('.gh-projects-options-toggle-repo').on('click', toggleRepositoryEvent);
}

function toggleRepositoryEvent(e) {
  const repo = e.target.id;
  const checked = e.target.checked;

  const repoPattern = `.*\\/${repo}#\\d+ .*`;
  const cards = $('.issue-card');
  cards.each((index, card) => {
    const cardDesc = $(card).find('small')[0];
    if (cardDesc.innerText.match(repoPattern)) {
      if (checked) {
        $(card).hide();
      } else {
        $(card).show();
      }
    }
  });
}

function toggleAddColumnEvent(e) {
  const checked = e.target.checked;

  const addColumnEl = $('.js-new-project-column-container');
  if (checked) {
    $(addColumnEl).hide();
  } else {
    $(addColumnEl).show();
  }
}

function toggleProjectDetailsEvent(e) {
  const checked = e.target.checked;

  const projectDetailsEl = $('.js-toggle-project-about').parent();
  if (checked) {
    $(projectDetailsEl).removeClass('d-table-cell').hide();
  } else {
    $(projectDetailsEl).addClass('d-table-cell').show();
  }
}

function toggleProjectSettingsEvent(e) {
  const checked = e.target.checked;

  const settingsEl = $('div.project-header > div.d-table.mt-1.float-right.f6').children().last();
  if (checked) {
    $(settingsEl).removeClass('d-table-cell').hide();
  } else {
    $(settingsEl).addClass('d-table-cell').show();
  }
}

$(document).ready(() => {
  addHeaderOptionsIcon();
  addAssigneesFilter();
  addProjectsOptionsPane();
  addButtonsEventListeners();
});
<commit_msg>Enhance filter by labels<commit_after>let selectedAssignees = [];
let selectedLabels = [];

function addHeaderOptionsIcon() {
  const headerButtons = $('div.project-header > div.d-table.mt-1.float-right.f6');
  headerButtons.prepend(Handlebars.templates['header-options-icon']());
}

function addProjectsOptionsPane() {
  const panes = $('div.project-pane');
  const panesParent = panes.parent();
  const isFullscreen = window.location.search.match(/fullscreen=true/);

  panesParent.append(Handlebars.templates['projects-options-pane']({ isFullscreen }));
}

function addButtonsEventListeners() {
  $('button.gh-projects-options-toggle').on('click', toggleProjectOptionsPane);
  $('#gh-projects-options-assignees-button').on('click', updateAssigneesList);
  $('#gh-projects-options-labels-button').on('click', updateLabelsList);
  $('#gh-projects-options-add-column-toggle').on('click', toggleAddColumnEvent);
  $('#gh-projects-options-project-details-toggle').on('click', toggleProjectDetailsEvent);
  $('#gh-projects-options-project-settings-toggle').on('click', toggleProjectSettingsEvent);
}

function toggleProjectOptionsPane() {
  const pane = $('div.project-pane.gh-projects-options-pane');
  if (pane.hasClass('d-none')) {
    pane.removeClass('d-none');

    openPane();
  } else {
    pane.addClass('d-none');
  }
}

function addAssigneesFilter() {
  const headerButtons = $('div.project-header > div.d-table.mt-1.float-right.f6');

  headerButtons.prepend(Handlebars.templates['assignees-filter']());
}

function updateAssigneesList() {
  const assigneesList = $('#gh-projects-options-assignees-list');
  const assignees = getAssigneesList();
  const template = Handlebars.templates['assignees-list'];

  assigneesList.html(template({ assignees }));
  $('.gh-projects-options-assignees-toggle').on('click', toggleAssigneeEvent);
}

function toggleAssigneeEvent(e) {
  const parent = $(e.target).parents('a.select-menu-item');
  const assignee = parent.length ? parent.attr('id') : $(e.target).attr('id');
  const cards = $('.issue-card');
  const index = selectedAssignees.indexOf(assignee);

  if (index === -1) {
    selectedAssignees.push(assignee);
  } else {
    selectedAssignees.splice(index, 1);
  }

  cards.each((index, card) => {
    if (!selectedAssignees.length) {
      $(card).show();
    } else {
      const cardAssignees = $(card).find('img.avatar');
      let firstAssignee = cardAssignees.length ? cardAssignees[0].alt.replace('@', '') : '#nobody';

      if (selectedAssignees.includes(firstAssignee)) {
        $(card).show();
      } else {
        $(card).hide();
      }
    }
  });

  updateAssigneesList();
  e.stopPropagation();
}

function addLabelsFilter() {
  const headerButtons = $('div.project-header > div.d-table.mt-1.float-right.f6');

  headerButtons.prepend(Handlebars.templates['labels-filter']());
}

function updateLabelsList() {
  const labelsList = $('#gh-projects-options-labels-list');
  const labels = getLabelsList();
  const template = Handlebars.templates['labels-list'];

  labelsList.html(template({ labels }));
  $('.gh-projects-options-labels-toggle').on('click', toggleLabelEvent);
}

function toggleLabelEvent(e) {
  const parent = $(e.target).parents('a.select-menu-item');
  const label = parent.length ? parent.attr('id') : $(e.target).attr('id');
  const cards = $('.issue-card');
  const index = selectedLabels.indexOf(label);

  if (index === -1) {
    selectedLabels.push(label);
  } else {
    selectedLabels.splice(index, 1);
  }

  cards.each((index, card) => {
    if (!selectedLabels.length) {
      $(card).show();
    } else {
      const cardLabels = $(card).find('.issue-card-label');
      let firstLabel = cardLabels.length ? cardLabels[0].innerText : '#unlabeled';

      if (selectedLabels.includes(firstLabel)) {
        $(card).show();
      } else {
        $(card).hide();
      }
    }
  });

  updateLabelsList();
  e.stopPropagation();
}

function getRepositoriesList() {
  const repositories = new Set();
  const cardsDesc = $('.issue-card').find('small');

  cardsDesc.each((index, cardDesc) => {
    const cardText = cardDesc.innerText.trim();
    const matchRepo = cardText.match(/.*\/(.*)#\d+ .*/);
    if (matchRepo && matchRepo.length > 1) {
      repositories.add(matchRepo[1]);
    }
  });

  return repositories;
}

function getAssigneesList() {
  const assignees = {};
  const assigneesEls = $('.project-columns img.avatar');

  assigneesEls.each((index, assignee) => {
    const username = assignee.alt.substring(1);
    const avatar = assignee.src;
    const isSelected = selectedAssignees.includes(username);

    if (assignees[username]) {
      assignees[username].count += 1;
    } else {
      assignees[username] = { avatar, username, isSelected, count: 1 };
    }
  });

  // Add the fake assignee "nobody"
  const nobodyUsername = '#nobody';
  assignees[nobodyUsername] = {
    username: nobodyUsername,
    isSelected: selectedAssignees.includes(nobodyUsername),
    isNobody: true
  };

  return Object.keys(assignees).map(x => assignees[x]).sort((a, b) => a.username.toLowerCase() > b.username.toLowerCase());
}

function getLabelsList() {
  const labels = [];
  const labelEls = $('.project-columns .issue-card-label');

  labelEls.each((index, label) => {
    const name = label.innerText;
    const style = label.style.cssText;
    const isSelected = selectedLabels.includes(name);

    if (labels[name]) {
      labels[name].count += 1;
    } else {
      labels[name] = { name, style, isSelected, count: 1 };
    }
  });

  // Add the fake label "empty"
  const unlabeledName = '#unlabeled';
  labels[unlabeledName] = {
    name: unlabeledName,
    isSelected: selectedLabels.includes(unlabeledName),
    isUnlabeled: true
  };

  return Object.keys(labels).map(x => labels[x]).sort((a, b) => a.name.toLowerCase() > b.name.toLowerCase());
}

function openPane() {
  // Hide repositories
  const repositories = Array.from(getRepositoriesList());

  $('#gh-projects-options-repolist').html(Handlebars.templates['repositories-checkboxes']({ repositories }));
  $('.gh-projects-options-toggle-repo').on('click', toggleRepositoryEvent);
}

function toggleRepositoryEvent(e) {
  const repo = e.target.id;
  const checked = e.target.checked;

  const repoPattern = `.*\\/${repo}#\\d+ .*`;
  const cards = $('.issue-card');
  cards.each((index, card) => {
    const cardDesc = $(card).find('small')[0];
    if (cardDesc.innerText.match(repoPattern)) {
      if (checked) {
        $(card).hide();
      } else {
        $(card).show();
      }
    }
  });
}

function toggleAddColumnEvent(e) {
  const checked = e.target.checked;

  const addColumnEl = $('.js-new-project-column-container');
  if (checked) {
    $(addColumnEl).hide();
  } else {
    $(addColumnEl).show();
  }
}

function toggleProjectDetailsEvent(e) {
  const checked = e.target.checked;

  const projectDetailsEl = $('.js-toggle-project-about').parent();
  if (checked) {
    $(projectDetailsEl).removeClass('d-table-cell').hide();
  } else {
    $(projectDetailsEl).addClass('d-table-cell').show();
  }
}

function toggleProjectSettingsEvent(e) {
  const checked = e.target.checked;

  const settingsEl = $('div.project-header > div.d-table.mt-1.float-right.f6').children().last();
  if (checked) {
    $(settingsEl).removeClass('d-table-cell').hide();
  } else {
    $(settingsEl).addClass('d-table-cell').show();
  }
}

$(document).ready(() => {
  addHeaderOptionsIcon();
  addAssigneesFilter();
  addLabelsFilter();
  addProjectsOptionsPane();
  addButtonsEventListeners();
});
<|endoftext|>


<commit_before>var EXTRACTORS_BY_HOSTNAME = {
  'www.google.com': extractFromPublicCodeSearch,
  'cs.corp.google.com': extractFromInternalCodeSearch,
  'trac.webkit.org': extractFromWebKitTrac,
  'src.chromium.org': extractFromChromium,
  'git.chromium.org': extractFromChromiumGit,
  '0.chrome_serve.web.web.grok.rv.borg.google.com': extractFromGrok,
  'code.google.com': extractFromCodesite
};

// TODO(mihaip): is this stable?
var PUBLIC_CODE_SEARCH_PATH_PREFIX = 'OAMlx_jo-ck';

var WEBKIT_REPOSITORY_PREFIX = 'chrome/trunk/src/third_party/WebKit/';
var WEBKIT_LAYOUT_PREFIX = 'LayoutTests/';
var WEBKIT_TRAC_BASE_BROWSER_PATH = 'http://trac.webkit.org/browser/trunk/';
var WEBKIT_TRAC_BASE_LOG_PATH = 'http://trac.webkit.org/log/trunk/';

var CHROMIUM_REPOSITORY_PREFIX = 'chrome/trunk/';
var CHROMIUM_VIEWEVC_PATH = 'http://src.chromium.org/viewvc/chrome/trunk/';
var CHROMIUM_GIT_PATH = 'http://git.chromium.org/gitweb/?p=chromium.git;hb=HEAD;f=';

var V8_REPOSITORY_PREFIX = 'chrome/trunk/src/v8/';
var V8_CODESITE_HOSTNAME = 'http://code.google.com';
var V8_CODESITE_BASE_PATH = '/trunk/';
var V8_CODESITE_BROWSE_PATH = '/p/v8/source/browse' + V8_CODESITE_BASE_PATH;
var V8_CODESITE_BASE_LOG_PATH = 'http://code.google.com/p/v8/source/list?path=' + V8_CODESITE_BASE_PATH;

var TRAC_ICON_URL = 'http://trac.webkit.org/chrome/common/trac.ico';
var CHROMIUM_ICON_URL = 'http://build.chromium.org/favicon.ico';
var GIT_ICON_URL = 'http://git.chromium.org/gitweb/static/git-favicon.png';
var CODE_SEARCH_ICON_URL = 'http://www.google.com/favicon.ico';
var GROK_ICON_URL = 'http://0.chrome_serve.web.web.grok.rv.borg.google.com/favicon.ico';
var CODESITE_ICON_URL = 'http://www.gstatic.com/codesite/ph/images/phosting.ico';

function extractFromPublicCodeSearch(url) {
  var fragment = goog.uri.utils.getFragment(url);

  if (goog.string.isEmptySafe(fragment)) {
    return null;
  }

  var path = fragment.split('&')[0];
  if (!goog.string.startsWith(path, PUBLIC_CODE_SEARCH_PATH_PREFIX)) {
    return null;
  }

  return extractFromChromiumRepositoryPath(
      'chrome/trunk' +
          goog.string.removeAt(path, 0, PUBLIC_CODE_SEARCH_PATH_PREFIX.length));
}

function extractFromInternalCodeSearch(url) {
  var fragment = goog.uri.utils.getFragment(url);

  if (goog.string.isEmptySafe(fragment)) {
    return null;
  }

  var path = fragment.split('&')[0];

  return extractFromChromiumRepositoryPath(path);
}

function extractFromGrok(url) {
  var path = goog.uri.utils.getParamValue(url, 'file');

  return extractFromChromiumRepositoryPath(path);
}

function extractFromChromiumRepositoryPath(path) {
  if (goog.string.isEmptySafe(path)) {
    return null;
  }

  if (goog.string.startsWith(path, V8_REPOSITORY_PREFIX)) {
    return new V8Link(goog.string.removeAt(path, 0, V8_REPOSITORY_PREFIX.length));
  }

  if (goog.string.startsWith(path, WEBKIT_REPOSITORY_PREFIX)) {
    var webKitPath =
        goog.string.removeAt(path, 0, WEBKIT_REPOSITORY_PREFIX.length);
    return WebKitLayoutTestLink.getTestPath(webKitPath)
        ? new WebKitLayoutTestLink(webKitPath)
        : new WebKitLink(webKitPath);
  }

  // Should be last
  if (goog.string.startsWith(path, CHROMIUM_REPOSITORY_PREFIX)) {
    return new ChromiumLink(
        goog.string.removeAt(path, 0, CHROMIUM_REPOSITORY_PREFIX.length));
  }

  return null;
}

function extractFromWebKitTrac(url) {
  var path = goog.uri.utils.getPath(url);

  var TRUNK_RE = new RegExp('/[^/]+/trunk/(.+)');
  var match = TRUNK_RE.exec(path);
  if (match) {
    return new WebKitLink(match[1]);
  }
}

function extractFromChromium(url) {
  var path = goog.uri.utils.getPath(url);

  var VIEWVC_TRUNK_SRC_RE = new RegExp('/viewvc/chrome/trunk/(.+)');
  var match = VIEWVC_TRUNK_SRC_RE.exec(path);
  if (match) {
    return new ChromiumLink(match[1]);
  }

  return null;
}

function extractFromChromiumGit(url) {
  var path = goog.uri.utils.getPath(url);

  if (path == '/gitweb/') {
    var query = goog.uri.utils.getQueryData(url);
    var pieces = query.split(';');
    var params = {};
    for (var i = 0; i < pieces.length; i++) {
      var keyAndValue = pieces[i].split('=');
      params[keyAndValue[0]] = keyAndValue[1];
    }
    if (params.p == 'chromium.git' && params.f) {
      return new ChromiumLink('src/' + params.f);
    }
  }
}

function extractFromCodesite(url) {
  var path = goog.uri.utils.getPath(url);

  if (goog.string.startsWith(path, '/p/v8/')) {
    if (goog.string.startsWith(path, V8_CODESITE_BROWSE_PATH)) {
      return new V8Link(goog.string.removeAt(path, 0, V8_CODESITE_BROWSE_PATH.length));
    }
    var pathParam = goog.uri.utils.getParamValue(url, 'path');
    if (goog.string.startsWith(pathParam, V8_CODESITE_BASE_PATH)) {
      return new V8Link(goog.string.removeAt(pathParam, 0, V8_CODESITE_BASE_PATH.length));
    }

    return null;
  }

  if (path == '/searchframe') {
    return extractFromPublicCodeSearch(url);
  }

  return null;
}

function WebKitLink(path) {
  Link.call(this, path);
  this.chromiumRepositoryPath = 'src/third_party/WebKit/' + path;
}
goog.inherits(WebKitLink, Link);

WebKitLink.prototype.addRelatedLinks = function(relatedLinks) {
  WebKitLink.superClass_.addRelatedLinks.call(this, relatedLinks);

  goog.array.extend(relatedLinks, [
    new RelatedLink(
        WEBKIT_TRAC_BASE_BROWSER_PATH + this.path,
        'Current version (Trac)',
        TRAC_ICON_URL),
    new RelatedLink(
        WEBKIT_TRAC_BASE_LOG_PATH + this.path,
        'Revision log (Trac)',
        TRAC_ICON_URL),
    new RelatedLink(
        WEBKIT_TRAC_BASE_BROWSER_PATH + this.path + '?annotate=blame',
        'Annotation (Trac)',
        TRAC_ICON_URL),
    new RelatedLink(
        'http://0.chrome_serve.web.web.grok.rv.borg.google.com/?file=chrome%2Ftrunk%2Fsrc%2Fthird_party%2FWebKit%2F' + encodeURIComponent(this.path),
        'Grok',
        GROK_ICON_URL,
        true)
  ]);
};

function WebKitLayoutTestLink(path) {
  WebKitLink.call(this, path);
  this.testPath = WebKitLayoutTestLink.getTestPath(path);
}
goog.inherits(WebKitLayoutTestLink, WebKitLink);

WebKitLayoutTestLink.getTestPath = function(path) {
  if (goog.string.startsWith(path, WEBKIT_LAYOUT_PREFIX)) {
    return goog.string.removeAt(path, 0, WEBKIT_LAYOUT_PREFIX.length);
  } else {
    return null;
  }
};

WebKitLayoutTestLink.prototype.addRelatedLinks = function(relatedLinks) {
  WebKitLayoutTestLink.superClass_.addRelatedLinks.call(this, relatedLinks);

  relatedLinks.push(new RelatedLink(
      'http://test-results.appspot.com/dashboards/flakiness_dashboard.html' +
          '#useWebKitCanary=true&tests=' + encodeURIComponent(this.testPath),
      'Chromium/WebKit Test History',
      CHROMIUM_ICON_URL));
}

function ChromiumLink(path) {
  Link.call(this, path);
  this.chromiumRepositoryPath = path;
}
goog.inherits(ChromiumLink, Link);

ChromiumLink.prototype.addRelatedLinks = function(relatedLinks) {
  ChromiumLink.superClass_.addRelatedLinks.call(this, relatedLinks);
  goog.array.extend(relatedLinks, [
    new RelatedLink(
        CHROMIUM_VIEWEVC_PATH + this.path + '?view=markup',
        'Current version (ViewVC)',
        CHROMIUM_ICON_URL),
    new RelatedLink(
        CHROMIUM_VIEWEVC_PATH + this.path + '?view=log',
        'Revision log (ViewVC)',
        CHROMIUM_ICON_URL),
    new RelatedLink(
        CHROMIUM_VIEWEVC_PATH + this.path + '?view=annotate',
        'Annotation (ViewVC)',
        CHROMIUM_ICON_URL),
    new RelatedLink(
        'http://0.chrome_serve.web.web.grok.rv.borg.google.com/?file=chrome%2Ftrunk%2F' + encodeURIComponent(this.path),
        'Grok',
        GROK_ICON_URL,
        true)
  ]);

  // Git repository only has the src/ subtree from the Chromium repository
  if (goog.string.startsWith(this.path, 'src/')) {
    var gitPath = goog.string.removeAt(this.path, 0, 'src/'.length);
    goog.array.extend(relatedLinks, [
      new RelatedLink(
          CHROMIUM_GIT_PATH + gitPath + ';a=blob',
          'Current version (Git)',
          GIT_ICON_URL),
      new RelatedLink(
          CHROMIUM_GIT_PATH + gitPath + ';a=history',
          'History (Git)',
          GIT_ICON_URL)
    ]);
  }
};

function V8Link(path) {
  Link.call(this, path);
  this.chromiumRepositoryPath = 'src/v8/' + path;
}
goog.inherits(V8Link, Link);

V8Link.prototype.addRelatedLinks = function(relatedLinks) {
  V8Link.superClass_.addRelatedLinks.call(this, relatedLinks);
  goog.array.extend(relatedLinks, [
    new RelatedLink(
        V8_CODESITE_HOSTNAME + V8_CODESITE_BROWSE_PATH + this.path,
        'Current version (code.google.com)',
        CODESITE_ICON_URL),
    new RelatedLink(
        V8_CODESITE_BASE_LOG_PATH + encodeURIComponent(this.path),
        'Revision Log (code.google.com)',
        CODESITE_ICON_URL)
  ]);
};

function RelatedLink(url, title, iconUrl, opt_internalOnly) {
  this.url = url;
  this.title = title;
  this.iconUrl = iconUrl;
  this.internalOnly = opt_internalOnly;
}

function Link(path) {
  this.path = path;
}

Link.prototype.addRelatedLinks = function(relatedLinks) {
  relatedLinks.push(
      new RelatedLink(
          // We can't generare a direct link to a file while using the Chromium
          // framing of Code Search, but searching for the file should be
          // equivalent, since Code Search will automatically open the file if
          // it's the only result.
          'http://code.google.com/p/chromium/source/search?q=file:^' +
              this.chromiumRepositoryPath + '$',
          'Code Search',
          CODE_SEARCH_ICON_URL));
};

Link.prototype.getRelatedLinks = function() {
  var relatedLinks = [];
  this.addRelatedLinks(relatedLinks);
  return relatedLinks;
}

Link.fromUrl = function(url) {
  var hostname = goog.uri.utils.getDomain(url);

  if (hostname in EXTRACTORS_BY_HOSTNAME) {
    return EXTRACTORS_BY_HOSTNAME[hostname](url);
  }

  return null;
};
<commit_msg>Add a Gitweb blame link<commit_after>var EXTRACTORS_BY_HOSTNAME = {
  'www.google.com': extractFromPublicCodeSearch,
  'cs.corp.google.com': extractFromInternalCodeSearch,
  'trac.webkit.org': extractFromWebKitTrac,
  'src.chromium.org': extractFromChromium,
  'git.chromium.org': extractFromChromiumGit,
  '0.chrome_serve.web.web.grok.rv.borg.google.com': extractFromGrok,
  'code.google.com': extractFromCodesite
};

// TODO(mihaip): is this stable?
var PUBLIC_CODE_SEARCH_PATH_PREFIX = 'OAMlx_jo-ck';

var WEBKIT_REPOSITORY_PREFIX = 'chrome/trunk/src/third_party/WebKit/';
var WEBKIT_LAYOUT_PREFIX = 'LayoutTests/';
var WEBKIT_TRAC_BASE_BROWSER_PATH = 'http://trac.webkit.org/browser/trunk/';
var WEBKIT_TRAC_BASE_LOG_PATH = 'http://trac.webkit.org/log/trunk/';

var CHROMIUM_REPOSITORY_PREFIX = 'chrome/trunk/';
var CHROMIUM_VIEWEVC_PATH = 'http://src.chromium.org/viewvc/chrome/trunk/';
var CHROMIUM_GIT_PATH = 'http://git.chromium.org/gitweb/?p=chromium.git;hb=HEAD;f=';

var V8_REPOSITORY_PREFIX = 'chrome/trunk/src/v8/';
var V8_CODESITE_HOSTNAME = 'http://code.google.com';
var V8_CODESITE_BASE_PATH = '/trunk/';
var V8_CODESITE_BROWSE_PATH = '/p/v8/source/browse' + V8_CODESITE_BASE_PATH;
var V8_CODESITE_BASE_LOG_PATH = 'http://code.google.com/p/v8/source/list?path=' + V8_CODESITE_BASE_PATH;

var TRAC_ICON_URL = 'http://trac.webkit.org/chrome/common/trac.ico';
var CHROMIUM_ICON_URL = 'http://build.chromium.org/favicon.ico';
var GIT_ICON_URL = 'http://git.chromium.org/gitweb/static/git-favicon.png';
var CODE_SEARCH_ICON_URL = 'http://www.google.com/favicon.ico';
var GROK_ICON_URL = 'http://0.chrome_serve.web.web.grok.rv.borg.google.com/favicon.ico';
var CODESITE_ICON_URL = 'http://www.gstatic.com/codesite/ph/images/phosting.ico';

function extractFromPublicCodeSearch(url) {
  var fragment = goog.uri.utils.getFragment(url);

  if (goog.string.isEmptySafe(fragment)) {
    return null;
  }

  var path = fragment.split('&')[0];
  if (!goog.string.startsWith(path, PUBLIC_CODE_SEARCH_PATH_PREFIX)) {
    return null;
  }

  return extractFromChromiumRepositoryPath(
      'chrome/trunk' +
          goog.string.removeAt(path, 0, PUBLIC_CODE_SEARCH_PATH_PREFIX.length));
}

function extractFromInternalCodeSearch(url) {
  var fragment = goog.uri.utils.getFragment(url);

  if (goog.string.isEmptySafe(fragment)) {
    return null;
  }

  var path = fragment.split('&')[0];

  return extractFromChromiumRepositoryPath(path);
}

function extractFromGrok(url) {
  var path = goog.uri.utils.getParamValue(url, 'file');

  return extractFromChromiumRepositoryPath(path);
}

function extractFromChromiumRepositoryPath(path) {
  if (goog.string.isEmptySafe(path)) {
    return null;
  }

  if (goog.string.startsWith(path, V8_REPOSITORY_PREFIX)) {
    return new V8Link(goog.string.removeAt(path, 0, V8_REPOSITORY_PREFIX.length));
  }

  if (goog.string.startsWith(path, WEBKIT_REPOSITORY_PREFIX)) {
    var webKitPath =
        goog.string.removeAt(path, 0, WEBKIT_REPOSITORY_PREFIX.length);
    return WebKitLayoutTestLink.getTestPath(webKitPath)
        ? new WebKitLayoutTestLink(webKitPath)
        : new WebKitLink(webKitPath);
  }

  // Should be last
  if (goog.string.startsWith(path, CHROMIUM_REPOSITORY_PREFIX)) {
    return new ChromiumLink(
        goog.string.removeAt(path, 0, CHROMIUM_REPOSITORY_PREFIX.length));
  }

  return null;
}

function extractFromWebKitTrac(url) {
  var path = goog.uri.utils.getPath(url);

  var TRUNK_RE = new RegExp('/[^/]+/trunk/(.+)');
  var match = TRUNK_RE.exec(path);
  if (match) {
    return new WebKitLink(match[1]);
  }
}

function extractFromChromium(url) {
  var path = goog.uri.utils.getPath(url);

  var VIEWVC_TRUNK_SRC_RE = new RegExp('/viewvc/chrome/trunk/(.+)');
  var match = VIEWVC_TRUNK_SRC_RE.exec(path);
  if (match) {
    return new ChromiumLink(match[1]);
  }

  return null;
}

function extractFromChromiumGit(url) {
  var path = goog.uri.utils.getPath(url);

  if (path == '/gitweb/') {
    var query = goog.uri.utils.getQueryData(url);
    var pieces = query.split(';');
    var params = {};
    for (var i = 0; i < pieces.length; i++) {
      var keyAndValue = pieces[i].split('=');
      params[keyAndValue[0]] = keyAndValue[1];
    }
    if (params.p == 'chromium.git' && params.f) {
      return new ChromiumLink('src/' + params.f);
    }
  }
}

function extractFromCodesite(url) {
  var path = goog.uri.utils.getPath(url);

  if (goog.string.startsWith(path, '/p/v8/')) {
    if (goog.string.startsWith(path, V8_CODESITE_BROWSE_PATH)) {
      return new V8Link(goog.string.removeAt(path, 0, V8_CODESITE_BROWSE_PATH.length));
    }
    var pathParam = goog.uri.utils.getParamValue(url, 'path');
    if (goog.string.startsWith(pathParam, V8_CODESITE_BASE_PATH)) {
      return new V8Link(goog.string.removeAt(pathParam, 0, V8_CODESITE_BASE_PATH.length));
    }

    return null;
  }

  if (path == '/searchframe') {
    return extractFromPublicCodeSearch(url);
  }

  return null;
}

function WebKitLink(path) {
  Link.call(this, path);
  this.chromiumRepositoryPath = 'src/third_party/WebKit/' + path;
}
goog.inherits(WebKitLink, Link);

WebKitLink.prototype.addRelatedLinks = function(relatedLinks) {
  WebKitLink.superClass_.addRelatedLinks.call(this, relatedLinks);

  goog.array.extend(relatedLinks, [
    new RelatedLink(
        WEBKIT_TRAC_BASE_BROWSER_PATH + this.path,
        'Current version (Trac)',
        TRAC_ICON_URL),
    new RelatedLink(
        WEBKIT_TRAC_BASE_LOG_PATH + this.path,
        'Revision log (Trac)',
        TRAC_ICON_URL),
    new RelatedLink(
        WEBKIT_TRAC_BASE_BROWSER_PATH + this.path + '?annotate=blame',
        'Annotation (Trac)',
        TRAC_ICON_URL),
    new RelatedLink(
        'http://0.chrome_serve.web.web.grok.rv.borg.google.com/?file=chrome%2Ftrunk%2Fsrc%2Fthird_party%2FWebKit%2F' + encodeURIComponent(this.path),
        'Grok',
        GROK_ICON_URL,
        true)
  ]);
};

function WebKitLayoutTestLink(path) {
  WebKitLink.call(this, path);
  this.testPath = WebKitLayoutTestLink.getTestPath(path);
}
goog.inherits(WebKitLayoutTestLink, WebKitLink);

WebKitLayoutTestLink.getTestPath = function(path) {
  if (goog.string.startsWith(path, WEBKIT_LAYOUT_PREFIX)) {
    return goog.string.removeAt(path, 0, WEBKIT_LAYOUT_PREFIX.length);
  } else {
    return null;
  }
};

WebKitLayoutTestLink.prototype.addRelatedLinks = function(relatedLinks) {
  WebKitLayoutTestLink.superClass_.addRelatedLinks.call(this, relatedLinks);

  relatedLinks.push(new RelatedLink(
      'http://test-results.appspot.com/dashboards/flakiness_dashboard.html' +
          '#useWebKitCanary=true&tests=' + encodeURIComponent(this.testPath),
      'Chromium/WebKit Test History',
      CHROMIUM_ICON_URL));
}

function ChromiumLink(path) {
  Link.call(this, path);
  this.chromiumRepositoryPath = path;
}
goog.inherits(ChromiumLink, Link);

ChromiumLink.prototype.addRelatedLinks = function(relatedLinks) {
  ChromiumLink.superClass_.addRelatedLinks.call(this, relatedLinks);
  goog.array.extend(relatedLinks, [
    new RelatedLink(
        CHROMIUM_VIEWEVC_PATH + this.path + '?view=markup',
        'Current version (ViewVC)',
        CHROMIUM_ICON_URL),
    new RelatedLink(
        CHROMIUM_VIEWEVC_PATH + this.path + '?view=log',
        'Revision log (ViewVC)',
        CHROMIUM_ICON_URL),
    new RelatedLink(
        CHROMIUM_VIEWEVC_PATH + this.path + '?view=annotate',
        'Annotation (ViewVC)',
        CHROMIUM_ICON_URL),
    new RelatedLink(
        'http://0.chrome_serve.web.web.grok.rv.borg.google.com/?file=chrome%2Ftrunk%2F' + encodeURIComponent(this.path),
        'Grok',
        GROK_ICON_URL,
        true)
  ]);

  // Git repository only has the src/ subtree from the Chromium repository
  if (goog.string.startsWith(this.path, 'src/')) {
    var gitPath = goog.string.removeAt(this.path, 0, 'src/'.length);
    goog.array.extend(relatedLinks, [
      new RelatedLink(
          CHROMIUM_GIT_PATH + gitPath + ';a=blob',
          'Current version (Gitweb)',
          GIT_ICON_URL),
      new RelatedLink(
          CHROMIUM_GIT_PATH + gitPath + ';a=history',
          'History (Gitweb)',
          GIT_ICON_URL),
      new RelatedLink(
          CHROMIUM_GIT_PATH + gitPath + ';a=blame',
          'Blame (Gitweb)',
          GIT_ICON_URL)
    ]);
  }
};

function V8Link(path) {
  Link.call(this, path);
  this.chromiumRepositoryPath = 'src/v8/' + path;
}
goog.inherits(V8Link, Link);

V8Link.prototype.addRelatedLinks = function(relatedLinks) {
  V8Link.superClass_.addRelatedLinks.call(this, relatedLinks);
  goog.array.extend(relatedLinks, [
    new RelatedLink(
        V8_CODESITE_HOSTNAME + V8_CODESITE_BROWSE_PATH + this.path,
        'Current version (code.google.com)',
        CODESITE_ICON_URL),
    new RelatedLink(
        V8_CODESITE_BASE_LOG_PATH + encodeURIComponent(this.path),
        'Revision Log (code.google.com)',
        CODESITE_ICON_URL)
  ]);
};

function RelatedLink(url, title, iconUrl, opt_internalOnly) {
  this.url = url;
  this.title = title;
  this.iconUrl = iconUrl;
  this.internalOnly = opt_internalOnly;
}

function Link(path) {
  this.path = path;
}

Link.prototype.addRelatedLinks = function(relatedLinks) {
  relatedLinks.push(
      new RelatedLink(
          // We can't generare a direct link to a file while using the Chromium
          // framing of Code Search, but searching for the file should be
          // equivalent, since Code Search will automatically open the file if
          // it's the only result.
          'http://code.google.com/p/chromium/source/search?q=file:^' +
              this.chromiumRepositoryPath + '$',
          'Code Search',
          CODE_SEARCH_ICON_URL));
};

Link.prototype.getRelatedLinks = function() {
  var relatedLinks = [];
  this.addRelatedLinks(relatedLinks);
  return relatedLinks;
}

Link.fromUrl = function(url) {
  var hostname = goog.uri.utils.getDomain(url);

  if (hostname in EXTRACTORS_BY_HOSTNAME) {
    return EXTRACTORS_BY_HOSTNAME[hostname](url);
  }

  return null;
};
<|endoftext|>


<commit_before>// @flow

import * as React from 'react';
import { createFragmentContainer, graphql } from 'react-relay';
import { View } from 'react-native';
import {
  StyleSheet,
  NetworkImage,
  Color,
  Price,
  Text,
} from '@kiwicom/react-native-app-shared';
import idx from 'idx';

import type { HotelDetailPreview_availability } from './__generated__/HotelDetailPreview_availability.graphql';

type ContainerProps = {|
  availability: $FlowFixMeProps,
|};

type Props = {
  ...ContainerProps,
  availability: ?HotelDetailPreview_availability,
};

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    height: 60,
  },
  image: {
    width: 60,
    height: 60,
    borderRadius: 3,
    marginRight: 10,
  },
  description: {
    flex: 1,
    justifyContent: 'space-between',
    marginVertical: 3,
  },
  hotelName: {
    fontSize: 16,
    color: '#30363d',
  },
  metainfo: {
    color: Color.grey.$700,
    opacity: 0.8,
    fontSize: 12,
  },
  price: {
    color: Color.brand,
    fontWeight: 'bold',
    fontSize: 12,
  },
});

const getReview = review => {
  if (review && review.count) {
    const reviewWord = review.count > 1 ? 'reviews' : 'review';
    const delimiter =
      Number.isFinite(review.score) || review.description ? ' · ' : '';

    return (
      <Text style={styles.metainfo}>
        {review.score} {review.description}
        {delimiter}
        {review.count} {reviewWord}
      </Text>
    );
  }

  return <Text style={styles.metainfo}>No reviews</Text>;
};

export class HotelDetailPreview extends React.Component<Props> {
  render = () => {
    const { availability } = this.props;
    const name = idx(availability, _ => _.hotel.name);
    const price = idx(availability, _ => _.price) || {};
    const image = idx(availability, _ => _.hotel.mainPhoto.thumbnailUrl);
    const review = idx(availability, _ => _.hotel.review);

    return (
      <View style={[styles.container]}>
        <View>
          <NetworkImage
            style={styles.image}
            source={{
              uri: image,
            }}
            resizeMode="contain"
          />
        </View>
        <View style={styles.description}>
          <Text style={styles.hotelName} numberOfLines={1}>
            {name}
          </Text>
          {getReview(review)}
          <Text style={styles.price}>
            {price &&
              price.currency &&
              price.amount && (
                <Price currency={price.currency} amount={price.amount} />
              )}
          </Text>
        </View>
      </View>
    );
  };
}

export default (createFragmentContainer(
  HotelDetailPreview,
  graphql`
    fragment HotelDetailPreview_availability on HotelAvailability {
      price {
        amount
        currency
      }
      hotel {
        id
        name
        mainPhoto {
          thumbnailUrl
        }
        rating {
          stars
        }
        review {
          score
          description
          count
        }
      }
    }
  `,
): React.ComponentType<ContainerProps>);
<commit_msg>Adjust hotel detail preview styling<commit_after>// @flow

import * as React from 'react';
import { createFragmentContainer, graphql } from 'react-relay';
import { View } from 'react-native';
import {
  StyleSheet,
  NetworkImage,
  Color,
  Price,
  Text,
} from '@kiwicom/react-native-app-shared';
import idx from 'idx';

import type { HotelDetailPreview_availability } from './__generated__/HotelDetailPreview_availability.graphql';

type ContainerProps = {|
  availability: $FlowFixMeProps,
|};

type Props = {
  ...ContainerProps,
  availability: ?HotelDetailPreview_availability,
};

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    height: 60,
  },
  image: {
    width: 60,
    height: 60,
    borderRadius: 3,
    marginRight: 10,
  },
  description: {
    flex: 1,
    justifyContent: 'space-between',
  },
  hotelName: {
    fontSize: 16,
    color: '#30363d',
    android: {
      lineHeight: 17,
    },
  },
  metainfo: {
    color: Color.grey.$700,
    opacity: 0.8,
    fontSize: 12,
    android: {
      lineHeight: 13,
    },
  },
  price: {
    color: Color.brand,
    fontWeight: 'bold',
    fontSize: 12,
    android: {
      lineHeight: 13,
    },
  },
});

const getReview = review => {
  if (review && review.count) {
    const reviewWord = review.count > 1 ? 'reviews' : 'review';
    const delimiter =
      Number.isFinite(review.score) || review.description ? ' · ' : '';

    return (
      <Text style={styles.metainfo}>
        {review.score} {review.description}
        {delimiter}
        {review.count} {reviewWord}
      </Text>
    );
  }

  return <Text style={styles.metainfo}>No reviews</Text>;
};

export class HotelDetailPreview extends React.Component<Props> {
  render = () => {
    const { availability } = this.props;
    const name = idx(availability, _ => _.hotel.name);
    const price = idx(availability, _ => _.price) || {};
    const image = idx(availability, _ => _.hotel.mainPhoto.thumbnailUrl);
    const review = idx(availability, _ => _.hotel.review);

    return (
      <View style={[styles.container]}>
        <View>
          <NetworkImage
            style={styles.image}
            source={{
              uri: image,
            }}
            resizeMode="contain"
          />
        </View>
        <View style={styles.description}>
          <Text style={styles.hotelName} numberOfLines={1}>
            {name}
          </Text>
          {getReview(review)}
          <Text style={styles.price}>
            {price &&
              price.currency &&
              price.amount && (
                <Price currency={price.currency} amount={price.amount} />
              )}
          </Text>
        </View>
      </View>
    );
  };
}

export default (createFragmentContainer(
  HotelDetailPreview,
  graphql`
    fragment HotelDetailPreview_availability on HotelAvailability {
      price {
        amount
        currency
      }
      hotel {
        id
        name
        mainPhoto {
          thumbnailUrl
        }
        rating {
          stars
        }
        review {
          score
          description
          count
        }
      }
    }
  `,
): React.ComponentType<ContainerProps>);
<|endoftext|>


<commit_before>/* eslint-disable import/first */
import { combineReducers } from 'redux';
import { handleActions } from 'utils/redux';

// Routes
import router from './router';

// Pages
import * as country from 'pages/country/components/root/root';

const countryReducers = {
  root: handleActions(country)
};

// Components
import * as ShareComponent from 'components/share/share';
import * as headerComponent from 'pages/country/components/header/header';
import * as mapComponent from 'pages/country/components/map/map';
import * as widgetHeaderComponent from 'pages/country/components/widget-header/widget-header';
import * as widgetAreasMostCoverGainComponent from 'pages/country/components/widget-areas-most-cover-gain/widget-areas-most-cover-gain';
import * as widgetPlantationAreaComponent from 'pages/country/components/widget-plantation-area/widget-plantation-area';
import * as widgetStoriesComponent from 'pages/country/components/widget-stories/widget-stories';
import * as widgetTotalAreaPlantationsComponent from 'pages/country/components/widget-total-area-plantations/widget-total-area-plantations';
import * as widgetTreeCoverComponent from 'pages/country/components/widget-tree-cover/widget-tree-cover';
import * as widgetTreeCoverGainComponent from 'pages/country/components/widget-tree-cover-gain/widget-tree-cover-gain';
import * as widgetTreeCoverLossAreasComponent from 'pages/country/components/widget-tree-cover-loss-areas/widget-tree-cover-loss-areas';
import * as widgetTreeLocatedComponent from 'pages/country/components/widget-tree-located/widget-tree-located';
import * as widgetTreeLossComponent from 'pages/country/components/widget-tree-loss/widget-tree-loss';

const componentsReducers = {
  share: handleActions(ShareComponent),
  header: handleActions(headerComponent),
  map: handleActions(mapComponent),
  widgetHeader: handleActions(widgetHeaderComponent),
  widgetAreasMostCoverGain: handleActions(widgetAreasMostCoverGainComponent),
  widgetPlantationArea: handleActions(widgetPlantationAreaComponent),
  widgetStories: handleActions(widgetStoriesComponent),
  widgetTotalAreaPlantations: handleActions(
    widgetTotalAreaPlantationsComponent
  ),
  widgetTreeCover: handleActions(widgetTreeCoverComponent),
  widgetTreeCoverGain: handleActions(widgetTreeCoverGainComponent),
  widgetTreeCoverLossAreas: handleActions(widgetTreeCoverLossAreasComponent),
  widgetTreeLocated: handleActions(widgetTreeLocatedComponent),
  widgetTreeLoss: handleActions(widgetTreeLossComponent)
};

export default combineReducers({
  ...countryReducers,
  ...componentsReducers,
  location: router.reducer
});
<commit_msg>move components to /country folder level<commit_after>/* eslint-disable import/first */
import { combineReducers } from 'redux';
import { handleActions } from 'utils/redux';

// Routes
import router from './router';

// Pages
import * as country from 'pages/country/root/root';

const countryReducers = {
  root: handleActions(country)
};

// Components
import * as ShareComponent from 'components/share/share';
import * as headerComponent from 'pages/country/header/header';
import * as mapComponent from 'pages/country/map/map';
import * as widgetHeaderComponent from 'pages/country/widget-header/widget-header';
import * as widgetAreasMostCoverGainComponent from 'pages/country/widget-areas-most-cover-gain/widget-areas-most-cover-gain';
import * as widgetPlantationAreaComponent from 'pages/country/widget-plantation-area/widget-plantation-area';
import * as widgetStoriesComponent from 'pages/country/widget-stories/widget-stories';
import * as widgetTotalAreaPlantationsComponent from 'pages/country/widget-total-area-plantations/widget-total-area-plantations';
import * as widgetTreeCoverComponent from 'pages/country/widget-tree-cover/widget-tree-cover';
import * as widgetTreeCoverGainComponent from 'pages/country/widget-tree-cover-gain/widget-tree-cover-gain';
import * as widgetTreeCoverLossAreasComponent from 'pages/country/widget-tree-cover-loss-areas/widget-tree-cover-loss-areas';
import * as widgetTreeLocatedComponent from 'pages/country/widget-tree-located/widget-tree-located';
import * as widgetTreeLossComponent from 'pages/country/widget-tree-loss/widget-tree-loss';

const componentsReducers = {
  share: handleActions(ShareComponent),
  header: handleActions(headerComponent),
  map: handleActions(mapComponent),
  widgetHeader: handleActions(widgetHeaderComponent),
  widgetAreasMostCoverGain: handleActions(widgetAreasMostCoverGainComponent),
  widgetPlantationArea: handleActions(widgetPlantationAreaComponent),
  widgetStories: handleActions(widgetStoriesComponent),
  widgetTotalAreaPlantations: handleActions(
    widgetTotalAreaPlantationsComponent
  ),
  widgetTreeCover: handleActions(widgetTreeCoverComponent),
  widgetTreeCoverGain: handleActions(widgetTreeCoverGainComponent),
  widgetTreeCoverLossAreas: handleActions(widgetTreeCoverLossAreasComponent),
  widgetTreeLocated: handleActions(widgetTreeLocatedComponent),
  widgetTreeLoss: handleActions(widgetTreeLossComponent)
};

export default combineReducers({
  ...countryReducers,
  ...componentsReducers,
  location: router.reducer
});
<|endoftext|>


<commit_before>/*
 * Copyright 2014 Thoughtworks Inc.
 *
 * 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.
 */
define([
    'flight/lib/component',
    'component/mixins/with_auth_token_from_hash',
  ],
  function(defineComponent, withAuthTokemFromHash) {
    'use strict';
    return defineComponent(issuesExporter, withAuthTokemFromHash);

    function issuesExporter() {
      var issuesToExport = [];

      var csvLink = '';

      this.cleanLabel = function(label) {
        return label.trim().
               replace(/^[^a-zA-Z]+/g, '').
               replace(/[^a-zA-Z[0-9]\s]/g, '').
               replace(/\s/g, '_').
               toLowerCase();
      };

      this.mountExportCsvLink = function(ev, data) {
        var repositoriesUrls = this.getRepositoriesUrlsFromIssues(data.issues);
        issuesToExport = data.issues;
        this.getEventsFromProjects(repositoriesUrls);
      };

      this.clearLink = function() {
        csvLink = '';
      };

      this.showExportingFeedbackLink = function(){
        $('#export_csv').hide();
        $('#export_csv').after('EXPORTING');
      };

      this.showExportCsvLink = function(){
        $('#exporting_csv').remove();
        $('#export_csv').show();
      };

      this.linkToCsv = function(data) {
        var issuesCsv = this.issuesToCsv(data);
        if (csvLink === '') {
          issuesCsv.splice(0, 0, this.csvHeader());
        }

        csvLink += encodeURIComponent(issuesCsv.join('\n') + '\n');
        return 'data:text/csv;charset=utf8,' + csvLink;
      };

      this.validIssuesToExport = function(issues) {
        return _.filter(issues, function(issue) {
          return issue.projectName !== undefined;
        });
      };

      this.issuesToCsv = function(issues) {
        var issuesCsv = _.map(this.validIssuesToExport(issues), function(issue) {
          var custom_columns = ['1 - Ready', '2 - Development', '3 - Quality Assurance'];
          var data = ['\"' + issue.projectName + "\"",
            issue.number,
            "\"" + issue.title + "\"",
            issue.state,
            issue.kanbanState,
            "\"" + _.map(issue.labels, function(label) {
              return label.name;
            }) + "\"",
            issue.created_at,
            issue.closed_at,
            daysBetween(issue.created_at, issue.closed_at),
          ];
          _.each(custom_columns, function(column) {
            data.push(issue[this.cleanLabel(column)+"_at"]);
          }, this);
          return data.join(';');
        }, this);

        return issuesCsv;
      };

      function daysBetween(earlierDate, lateDate) {
        if ((earlierDate && lateDate) && (lateDate > earlierDate)) {
          var leadTime,
            millisecondsInDay = 86400000;

          leadTime = (new Date(lateDate) - new Date(earlierDate)) / millisecondsInDay;
          leadTime = parseInt(leadTime);

          return leadTime;
        } else {
          return "";
        }
      };

      this.csvHeader = function() {
        var labels = ['1 - Ready', '2 - Development', '3 - Quality Assurance'];
        var header = ["Source", "Github ID", "Title", "Status", "Kanban State", "Tags", "Create at", "Closed at", "Lead Time"]
        _.each(labels, function(label) {
          header.push(this.cleanLabel(label) + " at");
        }, this);
        return header.join(';');
      };

      this.getEventsFromProjects = function(projectsUrl) {
        var events = [];
        var self = this;

        function getEvents(url) {
          $.get(url, function(data, textStatus, request) {
            events = events.concat(data);

            try {
              var header = request.getResponseHeader('Link');
              var next = header.match(/<.*?>; rel="next"/)[0].match(/https.*[0-9]+/)[0];
              if (next) {
                getEvents(next);
              }
            } catch (err) {}
          });
        }

        $.each(projectsUrl, function(index, val) {
          getEvents(val + 'issues/events?per_page=100&access_token=' + this.getCurrentAuthToken());
        }.bind(self));

        $(document).one('ajaxStop', function() {
          self.createCsvUri(self.addEventDateForIssues(issuesToExport, events));
        });
      };

      this.groupEventsByIssuesId = function(events) {
        return _.groupBy(events, function(event) {
          return event.issue.id;
        });
      };

      this.excludeNonLabeledEvents = function(mappedEvents) {
        return _.object(_.map(mappedEvents, function(issueEvents, key) {
          return [key, _.filter(issueEvents, function(event) {
            return event.event == 'labeled';
          })]
        }));
      };

      this.getIssueEventsByLabel = function(labeledEvents, label) {
        return _.object(_.map(labeledEvents, function(issueEvents, key) {
          return [key, _.filter(issueEvents, function(event) {
            return event.label.name == label;
          })]
        }));
      };

      this.getEarliestIssueEvents = function(labeledEvents) {
        return _.object(_.map(labeledEvents, function(events, key) {
          return [key, _.first(_.sortBy(events, function(e) {
            return e.created_at;
          }))]
        }));
      };

      this.mergeEventsWithIssues = function(issues, events, key) {
        return _.each(issues, function(issue) {
          if (events[issue.id]) {
            issue[key] = events[issue.id].created_at;
          }
        });
      };

      this.getRepositoriesUrlsFromIssues = function(issues) {
        return _.uniq(_.pluck(issues, 'repoUrl'));
      };

      this.addEventDateForIssues = function(issues, events) {
        var groupedEventsByIssueId = {},
          labeledEvents = {},
          labelEvents = {},
          earlierstIssuesEvent = {},
          issuesWithEventDate = issues,
          labels = ['1 - Ready', '2 - Development', '3 - Quality Assurance'];

        groupedEventsByIssueId = this.groupEventsByIssuesId(events);
        labeledEvents = this.excludeNonLabeledEvents(groupedEventsByIssueId);
        _.each(labels, function(label) {
          labelEvents = this.getIssueEventsByLabel(labeledEvents, label);
          earlierstIssuesEvent = this.getEarliestIssueEvents(labelEvents);
          issuesWithEventDate = this.mergeEventsWithIssues(issuesWithEventDate, earlierstIssuesEvent, this.cleanLabel(label)+"_at")
        }, this);

        return issuesWithEventDate;
      };

      this.createCsvUri = function(issuesWithEventDate) {
        var uri = this.linkToCsv(issuesWithEventDate);

        var downloadLink = document.createElement("a");
        downloadLink.href = uri;
        downloadLink.download = "data.csv";

        document.body.appendChild(downloadLink);
        downloadLink.click();
        document.body.removeChild(downloadLink);
        issuesToExport = [];
        this.trigger('data:issues:clearExportCsvLink');

      };

      this.after('initialize', function() {
        this.on('data:issues:mountExportCsvLink', this.mountExportCsvLink);
        this.on('data:issues:mountExportCsvLink', this.showExportingFeedbackLink);
        this.on('data:issues:clearExportCsvLink', this.clearLink);
        this.on('data:issues:clearExportCsvLink', this.showExportCsvLink);

      });
    }
  }
);
<commit_msg>Unify the columns in one array on the issues exporter<commit_after>/*
 * Copyright 2014 Thoughtworks Inc.
 *
 * 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.
 */
define([
    'flight/lib/component',
    'component/mixins/with_auth_token_from_hash',
  ],
  function(defineComponent, withAuthTokemFromHash) {
    'use strict';
    return defineComponent(issuesExporter, withAuthTokemFromHash);

    function issuesExporter() {
      var issuesToExport = [];

      var csvLink = '';

      var customColumns = ['1 - Ready', '2 - Development', '3 - Quality Assurance'];

      this.cleanLabel = function(label) {
        return label.trim().
               replace(/^[^a-zA-Z]+/g, '').
               replace(/[^a-zA-Z[0-9]\s]/g, '').
               replace(/\s/g, '_').
               toLowerCase();
      };

      this.mountExportCsvLink = function(ev, data) {
        var repositoriesUrls = this.getRepositoriesUrlsFromIssues(data.issues);
        issuesToExport = data.issues;
        this.getEventsFromProjects(repositoriesUrls);
      };

      this.clearLink = function() {
        csvLink = '';
      };

      this.showExportingFeedbackLink = function(){
        $('#export_csv').hide();
        $('#export_csv').after('EXPORTING');
      };

      this.showExportCsvLink = function(){
        $('#exporting_csv').remove();
        $('#export_csv').show();
      };

      this.linkToCsv = function(data) {
        var issuesCsv = this.issuesToCsv(data);
        if (csvLink === '') {
          issuesCsv.splice(0, 0, this.csvHeader());
        }

        csvLink += encodeURIComponent(issuesCsv.join('\n') + '\n');
        return 'data:text/csv;charset=utf8,' + csvLink;
      };

      this.validIssuesToExport = function(issues) {
        return _.filter(issues, function(issue) {
          return issue.projectName !== undefined;
        });
      };

      this.issuesToCsv = function(issues) {
        var issuesCsv = _.map(this.validIssuesToExport(issues), function(issue) {
          var data = ['\"' + issue.projectName + "\"",
            issue.number,
            "\"" + issue.title + "\"",
            issue.state,
            issue.kanbanState,
            "\"" + _.map(issue.labels, function(label) {
              return label.name;
            }) + "\"",
            issue.created_at,
            issue.closed_at,
            daysBetween(issue.created_at, issue.closed_at),
          ];
          _.each(customColumns, function(column) {
            data.push(issue[this.cleanLabel(column)+"_at"]);
          }, this);
          return data.join(';');
        }, this);

        return issuesCsv;
      };

      function daysBetween(earlierDate, lateDate) {
        if ((earlierDate && lateDate) && (lateDate > earlierDate)) {
          var leadTime,
            millisecondsInDay = 86400000;

          leadTime = (new Date(lateDate) - new Date(earlierDate)) / millisecondsInDay;
          leadTime = parseInt(leadTime);

          return leadTime;
        } else {
          return "";
        }
      };

      this.csvHeader = function() {
        var header = ["Source", "Github ID", "Title", "Status", "Kanban State", "Tags", "Create at", "Closed at", "Lead Time"]
        _.each(customColumns, function(label) {
          header.push(this.cleanLabel(label) + " at");
        }, this);
        return header.join(';');
      };

      this.getEventsFromProjects = function(projectsUrl) {
        var events = [];
        var self = this;

        function getEvents(url) {
          $.get(url, function(data, textStatus, request) {
            events = events.concat(data);

            try {
              var header = request.getResponseHeader('Link');
              var next = header.match(/<.*?>; rel="next"/)[0].match(/https.*[0-9]+/)[0];
              if (next) {
                getEvents(next);
              }
            } catch (err) {}
          });
        }

        $.each(projectsUrl, function(index, val) {
          getEvents(val + 'issues/events?per_page=100&access_token=' + this.getCurrentAuthToken());
        }.bind(self));

        $(document).one('ajaxStop', function() {
          self.createCsvUri(self.addEventDateForIssues(issuesToExport, events));
        });
      };

      this.groupEventsByIssuesId = function(events) {
        return _.groupBy(events, function(event) {
          return event.issue.id;
        });
      };

      this.excludeNonLabeledEvents = function(mappedEvents) {
        return _.object(_.map(mappedEvents, function(issueEvents, key) {
          return [key, _.filter(issueEvents, function(event) {
            return event.event == 'labeled';
          })]
        }));
      };

      this.getIssueEventsByLabel = function(labeledEvents, label) {
        return _.object(_.map(labeledEvents, function(issueEvents, key) {
          return [key, _.filter(issueEvents, function(event) {
            return event.label.name == label;
          })]
        }));
      };

      this.getEarliestIssueEvents = function(labeledEvents) {
        return _.object(_.map(labeledEvents, function(events, key) {
          return [key, _.first(_.sortBy(events, function(e) {
            return e.created_at;
          }))]
        }));
      };

      this.mergeEventsWithIssues = function(issues, events, key) {
        return _.each(issues, function(issue) {
          if (events[issue.id]) {
            issue[key] = events[issue.id].created_at;
          }
        });
      };

      this.getRepositoriesUrlsFromIssues = function(issues) {
        return _.uniq(_.pluck(issues, 'repoUrl'));
      };

      this.addEventDateForIssues = function(issues, events) {
        var groupedEventsByIssueId = {},
          labeledEvents = {},
          labelEvents = {},
          earlierstIssuesEvent = {},
          issuesWithEventDate = issues,

        groupedEventsByIssueId = this.groupEventsByIssuesId(events);
        labeledEvents = this.excludeNonLabeledEvents(groupedEventsByIssueId);
        _.each(customColumns, function(label) {
          labelEvents = this.getIssueEventsByLabel(labeledEvents, label);
          earlierstIssuesEvent = this.getEarliestIssueEvents(labelEvents);
          issuesWithEventDate = this.mergeEventsWithIssues(issuesWithEventDate, earlierstIssuesEvent, this.cleanLabel(label)+"_at")
        }, this);

        return issuesWithEventDate;
      };

      this.createCsvUri = function(issuesWithEventDate) {
        var uri = this.linkToCsv(issuesWithEventDate);

        var downloadLink = document.createElement("a");
        downloadLink.href = uri;
        downloadLink.download = "data.csv";

        document.body.appendChild(downloadLink);
        downloadLink.click();
        document.body.removeChild(downloadLink);
        issuesToExport = [];
        this.trigger('data:issues:clearExportCsvLink');

      };

      this.after('initialize', function() {
        this.on('data:issues:mountExportCsvLink', this.mountExportCsvLink);
        this.on('data:issues:mountExportCsvLink', this.showExportingFeedbackLink);
        this.on('data:issues:clearExportCsvLink', this.clearLink);
        this.on('data:issues:clearExportCsvLink', this.showExportCsvLink);

      });
    }
  }
);
<|endoftext|>