<commit_before>var Utils         = require("../../utils")
  , AbstractQuery = require('../abstract/query')

module.exports = (function() {
  var Query = function(client, sequelize, callee, options) {
    this.client    = client
    this.callee    = callee
    this.sequelize = sequelize
    this.options   = Utils._.extend({
      logging: console.log,
      plain: false,
      raw: false
    }, options || {})

    this.checkLoggingOption()
  }

  Utils.inherit(Query, AbstractQuery)
  Query.prototype.run = function(sql) {
    this.sql = sql

    if (this.options.logging !== false) {
      this.options.logging('Executing: ' + this.sql)
    }

    var resultSet = [],
        errorDetected = false,
        self = this

    this.client.query(this.sql)
      .on('result', function(results) {

      results.on('row', function(row) {
          resultSet.push(row);
        })
        .on('error', function(err) {
          errorDetected = true
          self.emit('sql', this.sql)
          self.emit('error', err, this.callee)
        }.bind(this))
        .on('end', function(info) {
          if(errorDetected) {
            return
          }
          self.emit('sql', this.sql)
          // we need to figure out whether to send the result set
          // or info depending upon the type of query
          if( /^show/.test(this.sql.toLowerCase()) ||
            /^select/.test(this.sql.toLowerCase()) ||
            /^describe/.test(this.sql.toLowerCase()) ||
            ( resultSet.length && /^call/.test(this.sql.toLowerCase()) ) ) {
            self.emit('success', this.formatResults(resultSet))
          } else {
            self.emit('success', this.formatResults(info))
          }

        }.bind(this));
      }.bind(this))
      .on('error', function(err) {
        if(errorDetected) {
          return
        }
        errorDetected = true
        self.emit('sql', this.sql)
        self.emit('error', err, this.callee)
      }.bind(this))
      .on('end', function(info) {
        // nothing here (query info is returned during the 'result' event)

      }.bind(this)).setMaxListeners(100)

    return this
  }

  return Query
})()

<commit_msg>added code to deal with mariasql always returning strings<commit_after>var Utils         = require("../../utils")
  , AbstractQuery = require('../abstract/query')

module.exports = (function() {
  var Query = function(client, sequelize, callee, options) {
    this.client    = client
    this.callee    = callee
    this.sequelize = sequelize
    this.options   = Utils._.extend({
      logging: console.log,
      plain: false,
      raw: false
    }, options || {})

    this.checkLoggingOption()
  }

  Utils.inherit(Query, AbstractQuery)
  Query.prototype.run = function(sql) {
    this.sql = sql

    if (this.options.logging !== false) {
      this.options.logging('Executing: ' + this.sql)
    }

    var resultSet = [],
        errorDetected = false,
        self = this

    this.client.query(this.sql)
      .on('result', function(results) {

      results.on('row', function(row) {
          // iterate through each property to convert
          // strings into JS objects when possible
          for(var prop in row) {
            // take care of properties that shouldn't be strings
            if( row[prop] == null) {
              continue; // don't mess with null
            } else if( parseIso8601.call(this, row[prop]) ) {
              // if string can be converted into a JS date, it probably
              // is a JS date -- replace the string with the date
              row[prop] = parseIso8601.call(this, row[prop])
            } else if( !isNaN(row[prop]) ) {
              // take care of strings that are really numbers
              row[prop] = +(row[prop]);
            }
          }
          resultSet.push(row)
        })
        .on('error', function(err) {
          errorDetected = true
          self.emit('sql', this.sql)
          self.emit('error', err, this.callee)
        }.bind(this))
        .on('end', function(info) {
          if(errorDetected) {
            return
          }
          self.emit('sql', this.sql)
          // we need to figure out whether to send the result set
          // or info depending upon the type of query
          if( /^show/.test(this.sql.toLowerCase()) ||
            /^select/.test(this.sql.toLowerCase()) ||
            /^describe/.test(this.sql.toLowerCase()) ||
            ( resultSet.length && /^call/.test(this.sql.toLowerCase()) ) ) {
            self.emit('success', this.formatResults(resultSet))
          } else {
            self.emit('success', this.formatResults(info))
          }

        }.bind(this));
      }.bind(this))
      .on('error', function(err) {
        if(errorDetected) {
          return
        }
        errorDetected = true
        self.emit('sql', this.sql)
        self.emit('error', err, this.callee)
      }.bind(this))
      .on('end', function(info) {
        // nothing here (query info is returned during the 'result' event)

      }.bind(this)).setMaxListeners(100)

    return this
  }

  var parseIso8601 = function(CurDate) {
    // note -- this function is borrowed from the following author
    // Author: Jim Davis, The Depressed Press of Boston
    // Library: DP_DateExtensions
    // Website: www.depressedpress.com

    // Check the input parameters
  if ( typeof CurDate != "string" || CurDate == "" ) {
    return null;
  };
    // Set the fragment expressions
  var S = "[\\-/:.]";
  var Yr = "((?:1[6-9]|[2-9][0-9])[0-9]{2})";
  var Mo = S + "((?:1[012])|(?:0[1-9])|[1-9])";
  var Dy = S + "((?:3[01])|(?:[12][0-9])|(?:0[1-9])|[1-9])";
  var Hr = "(2[0-4]|[01]?[0-9])";
  var Mn = S + "([0-5]?[0-9])";
  var Sd = "(?:" + S + "([0-5]?[0-9])(?:[.,]([0-9]+))?)?";
  var TZ = "(?:(Z)|(?:([\+\-])(1[012]|[0]?[0-9])(?::?([0-5]?[0-9]))?))?";
    // RegEx the input
    // First check: Just date parts (month and day are optional)
    // Second check: Full date plus time (seconds, milliseconds and TimeZone info are optional)
  var TF;
  if ( TF = new RegExp("^" + Yr + "(?:" + Mo + "(?:" + Dy + ")?)?" + "$").exec(CurDate) ) {} else if ( TF = new RegExp("^" + Yr + Mo + Dy + "[Tt ]" + Hr + Mn + Sd + TZ + "$").exec(CurDate) ) {};
    // If the date couldn't be parsed, return null
  if ( !TF ) { return null };
    // Default the Time Fragments if they're not present
  if ( !TF[2] ) { TF[2] = 1 } else { TF[2] = TF[2] - 1 };
  if ( !TF[3] ) { TF[3] = 1 };
  if ( !TF[4] ) { TF[4] = 0 };
  if ( !TF[5] ) { TF[5] = 0 };
  if ( !TF[6] ) { TF[6] = 0 };
  if ( !TF[7] ) { TF[7] = 0 };
  if ( !TF[8] ) { TF[8] = null };
  if ( TF[9] != "-" && TF[9] != "+" ) { TF[9] = null };
  if ( !TF[10] ) { TF[10] = 0 } else { TF[10] = TF[9] + TF[10] };
  if ( !TF[11] ) { TF[11] = 0 } else { TF[11] = TF[9] + TF[11] };
    // If there's no timezone info the data is local time
  if ( !TF[8] && !TF[9] ) {
    return new Date(TF[1], TF[2], TF[3], TF[4], TF[5], TF[6], TF[7]);
  };
    // If the UTC indicator is set the date is UTC
  if ( TF[8] == "Z" ) {
    return new Date(Date.UTC(TF[1], TF[2], TF[3], TF[4], TF[5], TF[6], TF[7]));
  };
    // If the date has a timezone offset
  if ( TF[9] == "-" || TF[9] == "+" ) {
      // Get current Timezone information
    var CurTZ = new Date().getTimezoneOffset();
    var CurTZh = TF[10] - ((CurTZ >= 0 ? "-" : "+") + Math.floor(Math.abs(CurTZ) / 60))
    var CurTZm = TF[11] - ((CurTZ >= 0 ? "-" : "+") + (Math.abs(CurTZ) % 60))
      // Return the date
    return new Date(TF[1], TF[2], TF[3], TF[4] - CurTZh, TF[5] - CurTZm, TF[6], TF[7]);
  };
    // If we've reached here we couldn't deal with the input, return null
  return null;

};

  return Query
})()

<|endoftext|>


<commit_before>String.prototype.toCamelCase = function(pascalCase) {
	var str = this.toLowerCase();

	var arr = str.split(/[\s-]/);

	for(var i = pascalCase ? 0 : 1; i < arr.length; i++) {
		arr[i] = arr[i].charAt(0).toUpperCase() + arr[i].slice(1);
	}

	return arr.join('');
};

String.prototype.toPascalCase = function() {
	return this.toCamelCase(true);
};
<commit_msg>Added toSpinalCase.<commit_after>var p = String.prototype;

// Converts spinal-case, snake_case or space case to camelCase
p.toCamelCase = function(pascalCase) {
	var str = this.toLowerCase();

	var arr = str.split(/[\s-_]/);

	for(var i = pascalCase ? 0 : 1; i < arr.length; i++) {
		arr[i] = arr[i].charAt(0).toUpperCase() + arr[i].slice(1);
	}

	return arr.join('');
};

// Converts spinal-case, snake_case or space case to PascalCase
p.toPascalCase = function() {
	return this.toCamelCase(true);
};

// converts camelCase or PascalCase to spinal-case/
p.toSpinalCase = function() {
	return (this.charAt(0).toLowerCase() + this.slice(1)).replace(/([A-Z])/, '-$&').toLowerCase();
};

// converts camelCase or PascalCase to snake_case/
p.toSnakeCase = function() {
	return (this.charAt(0).toLowerCase() + this.slice(1)).replace(/([A-Z])/, '_$&').toLowerCase();
};
<|endoftext|>


<commit_before>Hummingbird.SearchController = Ember.Controller.extend({
  filters: ["Everything", "Anime", "Manga", "User"],
  queryParams: ['query', 'filter'],
  performedSearch: false,
  userTypedSearch: false,
  searchResults: [],
  searchRequest: "",
  filter: "Everything",
  query: "",

  filteredSearchResults: function(){
    var self = this;
    return this.get('searchResults').filter(function(result){
      return (result.type == self.get('filter').toLowerCase() || "Everything" == self.get('filter'));
    });
  }.property('searchResults', 'filter'),

  // Devnote: For some reason, the Ember query parameter is not accesible
  // during init or after init._super(); This hack will trigger at the first
  // query change unless the seach term is <2 chars long. (Ember beta build)
  // On the first change, queryCounter actually still is set to zero.
  timesQueryChanged: 0,

  countQueryChanged: function (){
    this.incrementProperty('timesQueryChanged')
  }.observes('query'),

  forcePerformSearch: function(){
    if(!this.get('performedSearch') && !Ember.empty(this.get('query')))
      if(this.get('query').length > 2 && this.get('timesQueryChanged') == 0)
        this.performSearch();
  }.observes('query'),

  performSearch: function(){
    var self = this;

    if(this.get('query').length < 4){
      this.setProperties({
        'searchResults': [],
        'searchRequest': self.get('query')+' (too short, min. 4 chars)',
        'performedSearch': true
      });
      return;
    }

    ic.ajax({
      url: '/search.json?type=full&query='+this.get('query'),
      type: "GET"
    }).then(function(payload) {
      var query = self.get('query');
      self.setProperties({
        'searchResults': payload.search,
        'searchRequest': query,
        'performedSearch': true
      });
    });
  },

  hasquery: function () {
    return this.get('query').length !== 0;
  }.property('query'),

  actions: {
    submitSearch: function(){
      this.performSearch();
    }
  }
});
<commit_msg>Fix JSHint complaints.<commit_after>Hummingbird.SearchController = Ember.Controller.extend({
  filters: ["Everything", "Anime", "Manga", "User"],
  queryParams: ['query', 'filter'],
  performedSearch: false,
  userTypedSearch: false,
  searchResults: [],
  searchRequest: "",
  filter: "Everything",
  query: "",

  filteredSearchResults: function(){
    var self = this;
    return this.get('searchResults').filter(function(result){
      return (result.type === self.get('filter').toLowerCase() || "Everything" === self.get('filter'));
    });
  }.property('searchResults', 'filter'),

  // Devnote: For some reason, the Ember query parameter is not accesible
  // during init or after init._super(); This hack will trigger at the first
  // query change unless the seach term is <2 chars long. (Ember beta build)
  // On the first change, queryCounter actually still is set to zero.
  timesQueryChanged: 0,

  countQueryChanged: function (){
    this.incrementProperty('timesQueryChanged');
  }.observes('query'),

  forcePerformSearch: function(){
    if(!this.get('performedSearch') && !Ember.empty(this.get('query')))
      if(this.get('query').length > 2 && this.get('timesQueryChanged') === 0)
        this.performSearch();
  }.observes('query'),

  performSearch: function(){
    var self = this;

    if(this.get('query').length < 4){
      this.setProperties({
        'searchResults': [],
        'searchRequest': self.get('query')+' (too short, min. 4 chars)',
        'performedSearch': true
      });
      return;
    }

    ic.ajax({
      url: '/search.json?type=full&query='+this.get('query'),
      type: "GET"
    }).then(function(payload) {
      var query = self.get('query');
      self.setProperties({
        'searchResults': payload.search,
        'searchRequest': query,
        'performedSearch': true
      });
    });
  },

  hasquery: function () {
    return this.get('query').length !== 0;
  }.property('query'),

  actions: {
    submitSearch: function(){
      this.performSearch();
    }
  }
});
<|endoftext|>


<commit_before>/**
@license
Copyright (c) 2019 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/

import './boot.js';

const ShadyDOM = window.ShadyDOM;
const ShadyCSS = window.ShadyCSS;

/**
 * Ensure that elements in a ShadowDOM container are scoped correctly.
 * This function is only needed when ShadyDOM is used and unpatched DOM APIs are used in third party code.
 * This can happen in noPatch mode or when specialized APIs like ranges or tables are used to mutate DOM.
 *
 * @param  {!Element} container Container element to scope
 * @param  {boolean=} shouldObserve if true, start a mutation observer for added nodes to the container
 * @return {?MutationObserver} Returns a new MutationObserver on `container` if `shouldObserve` is true.
 */
export function scopeSubtree(container, shouldObserve = false) {
  // If using native ShadowDOM, abort
  if (!ShadyDOM || !ShadyCSS) {
    return null;
  }
  // ShadyCSS handles DOM mutations when ShadyDOM does not handle scoping itself
  if (!ShadyDOM['handlesDynamicScoping']) {
    return null;
  }
  const ScopingShim = ShadyCSS['ScopingShim'];
  // if ScopingShim is not available, abort
  if (!ScopingShim) {
    return null;
  }
  // capture correct scope for container
  const containerScope = ScopingShim['scopeForNode'](container);

  const scopify = (node) => {
    // NOTE: native qSA does not honor scoped DOM, but it is faster, and the same behavior as Polymer v1
    const descendants = ShadyDOM['nativeMethods']['querySelectorAll'].call(node, '*');
    const elements = [node, ...descendants];
    for (let i = 0; i < elements.length; i++) {
      const el = elements[i];
      const currentScope = ScopingShim['currentScopeForNode'](el);
      if (currentScope !== containerScope) {
        if (currentScope !== '') {
          ScopingShim['unscopeNode'](el, currentScope);
        }
        ScopingShim['scopeNode'](el, containerScope);
      }
    }
  };

  // scope everything in container
  scopify(container);

  if (shouldObserve) {
    const mo = new MutationObserver((mxns) => {
      for (let i = 0; i < mxns.length; i++) {
        const mxn = mxns[i];
        for (let j = 0; j < mxn.addedNodes.length; j++) {
          const addedNode = mxn.addedNodes[j];
          if (addedNode.nodeType === Node.ELEMENT_NODE) {
            scopify(addedNode);
          }
        }
      }
    });
    mo.observe(container, {childList: true, subtree: true});
    return mo;
  } else {
    return null;
  }
}<commit_msg>Use Array.from instead of a list comprehension<commit_after>/**
@license
Copyright (c) 2019 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/

import './boot.js';

const ShadyDOM = window.ShadyDOM;
const ShadyCSS = window.ShadyCSS;

/**
 * Ensure that elements in a ShadowDOM container are scoped correctly.
 * This function is only needed when ShadyDOM is used and unpatched DOM APIs are used in third party code.
 * This can happen in noPatch mode or when specialized APIs like ranges or tables are used to mutate DOM.
 *
 * @param  {!Element} container Container element to scope
 * @param  {boolean=} shouldObserve if true, start a mutation observer for added nodes to the container
 * @return {?MutationObserver} Returns a new MutationObserver on `container` if `shouldObserve` is true.
 */
export function scopeSubtree(container, shouldObserve = false) {
  // If using native ShadowDOM, abort
  if (!ShadyDOM || !ShadyCSS) {
    return null;
  }
  // ShadyCSS handles DOM mutations when ShadyDOM does not handle scoping itself
  if (!ShadyDOM['handlesDynamicScoping']) {
    return null;
  }
  const ScopingShim = ShadyCSS['ScopingShim'];
  // if ScopingShim is not available, abort
  if (!ScopingShim) {
    return null;
  }
  // capture correct scope for container
  const containerScope = ScopingShim['scopeForNode'](container);

  const scopify = (node) => {
    // NOTE: native qSA does not honor scoped DOM, but it is faster, and the same behavior as Polymer v1
    const elements = Array.from(ShadyDOM['nativeMethods']['querySelectorAll'].call(node, '*'));
    elements.push(node);
    for (let i = 0; i < elements.length; i++) {
      const el = elements[i];
      const currentScope = ScopingShim['currentScopeForNode'](el);
      if (currentScope !== containerScope) {
        if (currentScope !== '') {
          ScopingShim['unscopeNode'](el, currentScope);
        }
        ScopingShim['scopeNode'](el, containerScope);
      }
    }
  };

  // scope everything in container
  scopify(container);

  if (shouldObserve) {
    const mo = new MutationObserver((mxns) => {
      for (let i = 0; i < mxns.length; i++) {
        const mxn = mxns[i];
        for (let j = 0; j < mxn.addedNodes.length; j++) {
          const addedNode = mxn.addedNodes[j];
          if (addedNode.nodeType === Node.ELEMENT_NODE) {
            scopify(addedNode);
          }
        }
      }
    });
    mo.observe(container, {childList: true, subtree: true});
    return mo;
  } else {
    return null;
  }
}<|endoftext|>


<commit_before>(function() {
  window.shared || (window.shared = {});
  var dom = window.shared.ReactHelpers.dom;
  var createEl = window.shared.ReactHelpers.createEl;
  var merge = window.shared.ReactHelpers.merge;
  var QuadConverter = window.shared.QuadConverter;

  var ProfileChartSettings = window.ProfileChartSettings;
  var HighchartsWrapper = window.shared.HighchartsWrapper;

  // Component for all charts in the profile page.
  window.shared.ProfileChart = React.createClass({
    displayName: 'ProfileChart',

    propTypes: {
      quadSeries: React.PropTypes.arrayOf( // you can plot multiple series on the same graph
        React.PropTypes.shape({
          name: React.PropTypes.string.isRequired, // e.g. 'Scaled score'
          data: React.PropTypes.array.isRequired // [year, month, date, value] quads
        })
      ),
      titleText: React.PropTypes.string.isRequired, // e.g. 'MCAS scores, last 4 years'
      yAxis: React.PropTypes.object.isRequired, // options for rendering the y-axis
      student: React.PropTypes.object.isRequired
    },

    getDefaultProps: function() {
      var now = moment.utc().toDate();
      // TODO(kr) align to school year?
      // The intent of fixing this date range is that when staff are looking at profile of different students,
      // the scales are consistent (and not changing between 3 mos and 6 years depending on the student's record,
      // since that's easy to miss and misinterpret.
      var intervalBack = [4, 'years'];

      return {
        now: now,
        intervalBack: intervalBack,
        timestampRange: {
          min: moment(now).subtract(4, 'years').toDate().getTime(),
          max: now.getTime(),
        }
      };
    },

    render: function() {
      return createEl(HighchartsWrapper, merge(this.baseOptions(), {
        series: this.props.quadSeries.map(function(obj){
          return {
            name: obj.name,
            data: obj.data ? _.map(obj.data, QuadConverter.toPair): []
          }
        }),
        yAxis: this.props.yAxis
      }));
    },

    getPositionsForYearStarts: function(n, d, current_grade){
      var result = {};
      var current = d;

      n -= (current.month() + 1);
      current.dayOfYear(1);

      result[current.valueOf()] = current.year().toString() + "
" + "" + "Grade " + current_grade.toString() + "";

      // Take 12-month jumps backwards until we can't anymore.
      while (n - 12 > 0){
        current.subtract(1, 'year');
        n -= 12;
        current_grade -= 1;

        result[current.valueOf()] = current.year().toString() + "
" + "" +
          (current_grade != 0 ? "Grade " + current_grade.toString() + "" : "");
      }

      return result;
    },

    baseOptions: function() {
      var positionsForYearStarts = this.getPositionsForYearStarts(48, moment.utc(), parseInt(this.props.student.grade));

      return merge(ProfileChartSettings.base_options, {
        xAxis: [
          merge(ProfileChartSettings.x_axis_datetime, {
            plotLines: this.x_axis_bands,
            min: this.props.timestampRange.min,
            max: this.props.timestampRange.max
          }),
          {
            type: "datetime",
            offset: 35,
            linkedTo: 0,
            tickPositions: _.keys(positionsForYearStarts).map(Number),
            categories: positionsForYearStarts,
          }
        ]
      });
    },
  });
})();
<commit_msg>Added comments and slight refactoring.<commit_after>(function() {
  window.shared || (window.shared = {});
  var dom = window.shared.ReactHelpers.dom;
  var createEl = window.shared.ReactHelpers.createEl;
  var merge = window.shared.ReactHelpers.merge;
  var QuadConverter = window.shared.QuadConverter;

  var ProfileChartSettings = window.ProfileChartSettings;
  var HighchartsWrapper = window.shared.HighchartsWrapper;

  // Component for all charts in the profile page.
  window.shared.ProfileChart = React.createClass({
    displayName: 'ProfileChart',

    propTypes: {
      quadSeries: React.PropTypes.arrayOf( // you can plot multiple series on the same graph
        React.PropTypes.shape({
          name: React.PropTypes.string.isRequired, // e.g. 'Scaled score'
          data: React.PropTypes.array.isRequired // [year, month, date, value] quads
        })
      ),
      titleText: React.PropTypes.string.isRequired, // e.g. 'MCAS scores, last 4 years'
      yAxis: React.PropTypes.object.isRequired, // options for rendering the y-axis
      student: React.PropTypes.object.isRequired
    },

    getDefaultProps: function() {
      var now = moment.utc().toDate();
      // TODO(kr) align to school year?
      // The intent of fixing this date range is that when staff are looking at profile of different students,
      // the scales are consistent (and not changing between 3 mos and 6 years depending on the student's record,
      // since that's easy to miss and misinterpret.
      var intervalBack = [4, 'years'];

      return {
        now: now,
        intervalBack: intervalBack,
        timestampRange: {
          min: moment(now).subtract(4, 'years').toDate().getTime(),
          max: now.getTime(),
        }
      };
    },

    render: function() {
      return createEl(HighchartsWrapper, merge(this.baseOptions(), {
        series: this.props.quadSeries.map(function(obj){
          return {
            name: obj.name,
            data: obj.data ? _.map(obj.data, QuadConverter.toPair): []
          }
        }),
        yAxis: this.props.yAxis
      }));
    },

    getSchoolYearStartPositions: function(n, d, current_grade){
      // Takes in an integer (number of months back), the current date as a Moment object (UTC), and the student's current grade.
      // Returns an object mapping integer (tick position) --> string (school year starting at that position).

      var result = {};
      var current = d;

      n -= current.month() + 1;
      current.dayOfYear(1);

      result[current.valueOf()] = current.year().toString() + "
" + "" + "Grade " + current_grade.toString() + "";

      // Take 12-month jumps backwards until we can't anymore.
      while (n - 12 > 0){
        current.subtract(1, 'year');
        n -= 12;
        current_grade -= 1;

        result[current.valueOf()] = current.year().toString() + "
" + "" +
          (current_grade != 0 ? "Grade " + current_grade.toString() + "" : "");
      }

      return result;
    },

    baseOptions: function() {
      var positionsForYearStarts = this.getSchoolYearStartPositions(48, moment.utc(), parseInt(this.props.student.grade));

      return merge(ProfileChartSettings.base_options, {
        xAxis: [
          merge(ProfileChartSettings.x_axis_datetime, {
            plotLines: this.x_axis_bands,
            min: this.props.timestampRange.min,
            max: this.props.timestampRange.max
          }),
          {
            type: "datetime",
            offset: 35,
            linkedTo: 0,
            tickPositions: _.keys(positionsForYearStarts).map(Number),
            categories: positionsForYearStarts,
          }
        ]
      });
    },
  });
})();
<|endoftext|>


<commit_before>"use strict";

/**************************
 * Import important stuff *
 **************************/

const Application = require("../Application");
const Discord = require("discord.js");
const md2html = require("./md2html");
const MessageMap = require("../MessageMap");
const LatestDiscordMessageIds = require("./LatestDiscordMessageIds");
const handleEmbed = require("./handleEmbed");
const relayOldMessages = require("./relayOldMessages");
const Bridge = require("../bridgestuff/Bridge");

/***********
 * Helpers *
 ***********/

/**
 * Gets a list of bridges existing within a guild, where the bridge settings allow relaying join/leave messages
 *
 * @param {Discord.Guild} guild	The guild to get bridges for
 * @param {BridgeMap} bridgeMap	Map of the bridges that exist
 *
 * @returns {Bridge[]}	Bridges in the guild
 */
function getBridgesInGuild(guild, bridgeMap) {
	return guild.channels
		// Get the bridges corresponding to the channels in this guild
		.map(({id}) => bridgeMap.fromDiscordChannelId(id))
		// Remove the ones which are not bridged
		.filter((bridge) => bridge !== undefined);
}

/**********************
 * The setup function *
 **********************/

/**
 * Sets up the receiving of Discord messages, and relaying them to Telegram
 *
 * @param {Discord.Client} dcBot	The Discord bot
 * @param {BotAPI} tgBot	The Telegram bot
 * @param {MessageMap} messageMap	Map between IDs of messages
 * @param {BridgeMap} bridgeMap	Map of the bridges to use
 * @param {Settings} settings	Settings to use
 */
function setup(dcBot, tgBot, messageMap, bridgeMap, settings) {
	// Create the map of latest message IDs and bridges
	const latestDiscordMessageIds = new LatestDiscordMessageIds("latestDiscordMessageIds.json");

	// Listen for users joining the server
	dcBot.on("guildMemberAdd", (member) => {
		getBridgesInGuild(member.guild, bridgeMap)
			// Remove those which do not allow relaying join messages
			.filter((bridge) => bridge.discord.relayJoinMessages)
			// Ignore the T2D bridges
			.filter((bridge) => bridge.direction !== Bridge.DIRECTION_TELEGRAM_TO_DISCORD)
			// Send the join message to the corresponding Telegram chats
			.forEach(async (bridge) => {
				// Make the text to send
				const text = `<b>${member.displayName} (@${member.user.username})</b> joined the Discord side of the chat`;

				try {
					// Send it
					await tgBot.sendMessage({
						text,
						chat_id: bridge.telegram.chatId,
						parse_mode: "HTML"
					});
				} catch (err) {
					Application.logger.error(`[${bridge.name}] Could not notify Telegram about a user that joined Discord`, err);
				}
			});
	});

	// Listen for users joining the server
	dcBot.on("guildMemberRemove", (member) => {
		getBridgesInGuild(member.guild, bridgeMap)
			// Remove those which do not allow relaying leave messages
			.filter((bridge) => bridge.discord.relayLeaveMessages)
			// Ignore the T2D bridges
			.filter((bridge) => bridge.direction !== Bridge.DIRECTION_TELEGRAM_TO_DISCORD)
			// Send the leave message to the corresponding Telegram chats
			.forEach(async (bridge) => {
				// Make the text to send
				const text = `<b>${member.displayName} (@${member.user.username})</b> left the Discord side of the chat`;

				try {
					// Send it
					await tgBot.sendMessage({
						text,
						chat_id: bridge.telegram.chatId,
						parse_mode: "HTML"
					});
				} catch (err) {
					Application.logger.error(`[${bridge.name}] Could not notify Telegram about a user that left Discord`, err);
				}
			});
	});

	// Listen for Discord messages
	dcBot.on("message", async (message) => {

		// Ignore the bot's own messages
		if (message.author.id === dcBot.user.id) {
			return;
		}

		// Check if this is a request for server info
		if (message.channel.type === "text" && message.cleanContent.toLowerCase() === `@${dcBot.user.username} chatinfo`.toLowerCase()) {
			// It is. Give it
			message.reply(
				"serverId: " + message.guild.id + "\n" +
				"channelId: " + message.channel.id + "\n"
			);

			// Don't process the message any further
			return;
		}

		// Get info about the sender
		const senderName = message.author.username + (settings.telegram.colonAfterSenderName ? ":" : "");

		// Check if the message is from the correct chat
		const bridge = bridgeMap.fromDiscordChannelId(message.channel.id);
		if (bridge !== undefined) {
			// Ignore it if this is a telegram-to-discord bridge
			if (bridge.direction === Bridge.DIRECTION_TELEGRAM_TO_DISCORD) {
				return;
			}

			// This is now the latest message for this bridge
			latestDiscordMessageIds.setLatest(message.id, bridge);

			// Check for attachments and pass them on
			message.attachments.forEach(async ({url}) => {
				try {
					await tgBot.sendMessage({
						chat_id: bridge.telegram.chatId,
						text: `<b>${senderName}</b>\n<a href="${url}">${url}</a>`,
						parse_mode: "HTML"
					});
				} catch (err) {
					Application.logger.error(`[${bridge.name}] Telegram did not accept an attachment:`, err);
				}
			});

			// Check the message for embeds
			message.embeds.forEach((embed) => {
				// Ignore it if it is not a "rich" embed (image, link, video, ...)
				if (embed.type !== "rich") {
					return;
				}

				// Convert it to something Telegram likes
				const text = handleEmbed(embed, senderName);

				try {
					// Send it
					tgBot.sendMessage({
						text,
						chat_id: bridge.telegram.chatId,
						parse_mode: "HTML",
						disable_web_page_preview: true
					});
				} catch (err) {
					Application.logger.error(`[${bridge.name}] Telegram did not accept an embed:`, err);
				}
			});

			// Check if there is an ordinary text message
			if (message.cleanContent) {

				// Modify the message to fit Telegram
				const processedMessage = md2html(message.cleanContent);

				// Pass the message on to Telegram
				try {
					const tgMessage = await tgBot.sendMessage({
						chat_id: bridge.telegram.chatId,
						text: `<b>${senderName}</b>\n${processedMessage}`,
						parse_mode: "HTML"
					});

					// Make the mapping so future edits can work
					messageMap.insert(MessageMap.DISCORD_TO_TELEGRAM, message.id, tgMessage.message_id);
				} catch (err) {
					Application.logger.error(`[${bridge.name}] Telegram did not accept a message:`, err);
					Application.logger.error(`[${bridge.name}] Failed message:`, err);
				}
			}

		} else if (message.channel.guild === undefined || !bridgeMap.knownDiscordServer(message.channel.guild.id)) {	// Check if it is the correct server
			// The message is from the wrong chat. Inform the sender that this is a private bot
			message.reply(
				"This is an instance of a TediCross bot, bridging a chat in Telegram with one in Discord. "
				+ "If you wish to use TediCross yourself, please download and create an instance. "
				+ "You may join our Discord server (https://discord.gg/MfzGMzy) "
				+ "or Telegram group (https://t.me/TediCrossSupport) for help. "
				+ "See also https://github.com/TediCross/TediCross"
			);
		}
	});

	// Listen for message edits
	dcBot.on("messageUpdate", async (oldMessage, newMessage) => {
		// Don't do anything with the bot's own messages
		if (newMessage.author.id === dcBot.user.id) {
			return;
		}

		// Ignore it if it's not from a known bridge
		const bridge = bridgeMap.fromDiscordChannelId(newMessage.channel.id);
		if (bridge === undefined) {
			return;
		}

		try {
			// Get the corresponding Telegram message ID
			const tgMessageId = await messageMap.getCorresponding(MessageMap.DISCORD_TO_TELEGRAM, newMessage.id);

			// Get info about the sender
			const senderName = newMessage.author.username + (settings.telegram.colonAfterSenderName ? ":" : "");

			// Modify the message to fit Telegram
			const processedMessage = md2html(newMessage.cleanContent);

			// Send the update to Telegram
			await tgBot.editMessageText({
				chat_id: bridge.telegram.chatId,
				message_id: tgMessageId,
				text: `<b>${senderName}</b>\n${processedMessage}`,
				parse_mode: "HTML"
			});
		} catch (err) {
			Application.logger.error(`[${bridge.name}] Could not edit Telegram message:`, err);
		}
	});

	// Start the Discord bot
	dcBot.login(settings.discord.token)
		// Complain if it could not authenticate for some reason
		.catch((err) => Application.logger.error("Could not authenticate the Discord bot:", err));

	// Listen for the 'disconnected' event
	dcBot.on("disconnected", async (evt) => {
		Application.logger.error("Discord bot disconnected!", evt);

		bridgeMap.bridges.forEach(async (bridge) => {
			try {
				await tgBot.sendMessage({
					chat_id: bridge.telegram.chatId,
					text: "**TEDICROSS**\nThe discord side of the bot disconnected! Please check the log"
				});
			} catch (err) {
				Application.logger.error(`[${bridge.name}] Could not send message to Telegram:`, err);
			}
		});
	});

	// Listen for errors
	dcBot.on("error", (err) => {
		if (err.code === "ECONNRESET") {
			// Lost connection to the discord servers
			Application.logger.warn("Lost connection to Discord's servers. The bot will resume when connection is reestablished, which should happen automatically. If it does not, please report this to the TediCross support channel");
		} else {
			// Unknown error. Tell the user to tell the devs
			Application.logger.error("The Discord bot ran into an error. Please post the following error message in the TediCross support channel");
			Application.logger.error(err);
		}
	});

	// Listen for debug messages
	if (settings.debug) {
		dcBot.on("debug", (str) => {
			Application.logger.log(str);
		});

		// Check the Discord bot's status every now and then
		setInterval(() => {
			if (dcBot.status !== Discord.Constants.Status.READY) {
				let actualStatus = null;
				switch (dcBot.status) {
					case Discord.Constants.Status.CONNECTING:
						actualStatus = "CONNECTING";
						break;
					case Discord.Constants.Status.RECONNECTING:
						actualStatus = "RECONNECTING";
						break;
					case Discord.Constants.Status.IDLE:
						actualStatus = "IDLE";
						break;
					case Discord.Constants.Status.NEARLY:
						actualStatus = "NEARLY";
						break;
					case Discord.Constants.Status.DISCONNETED:
						actualStatus = "DISCONNECTED";
						break;
					default:
						actualStatus = "UNKNOWN";
						break;
				}
				Application.logger.error(`Discord status not ready! Status is '${actualStatus}'`);
			}
		}, 1000);
	}

	// Make a promise which resolves when the dcBot is ready
	dcBot.ready = new Promise((resolve) => {
		// Listen for the 'ready' event
		dcBot.once("ready", () => {
			// Log the event
			Application.logger.info(`Discord: ${dcBot.user.username} (${dcBot.user.id})`);

			// Mark the bot as ready
			resolve();
		});
	});

	// Relay old messages, if wanted by the user
	if (!settings.discord.skipOldMessages) {
		dcBot.ready = relayOldMessages(dcBot, latestDiscordMessageIds, bridgeMap);
	}
}

/*****************************
 * Export the setup function *
 *****************************/

module.exports = setup;
<commit_msg>Dried up join/leave code<commit_after>"use strict";

/**************************
 * Import important stuff *
 **************************/

const Application = require("../Application");
const Discord = require("discord.js");
const md2html = require("./md2html");
const MessageMap = require("../MessageMap");
const LatestDiscordMessageIds = require("./LatestDiscordMessageIds");
const handleEmbed = require("./handleEmbed");
const relayOldMessages = require("./relayOldMessages");
const Bridge = require("../bridgestuff/Bridge");

/***********
 * Helpers *
 ***********/

/**
 * Creates a function to give to 'guildMemberAdd' or 'guildMemberRemove' on a Discord bot
 *
 * @param {String} verb	Either "joined" or "left"
 * @param {BridgeMap} bridgeMap	Map of existing bridges
 * @param {BotAPI} tgBot	The Telegram bot to send the messages to
 *
 * @returns {Function}	Function which can be given to the 'guildMemberAdd' or 'guildMemberRemove' events of a Discord bot
 *
 * @private
 */
function makeJoinLeaveFunc(verb, bridgeMap, tgBot) {
	// Find out which setting property to check the bridges for
	const relaySetting = verb === "joined"
		? "relayJoinMessages"
		: "relayLeaveMessages"
	;

	return function(member) {
		// Get the bridges in the guild the member joined/left
		member.guild.channels
			// Get the bridges corresponding to the channels in this guild
			.map(({id}) => bridgeMap.fromDiscordChannelId(id))
			// Remove the ones which are not bridged
			.filter((bridge) => bridge !== undefined)
			// Remove those which do not allow relaying join messages
			.filter((bridge) => bridge.discord[relaySetting])
			// Ignore the T2D bridges
			.filter((bridge) => bridge.direction !== Bridge.DIRECTION_TELEGRAM_TO_DISCORD)
			.forEach(async (bridge) => {
				// Make the text to send
				const text = `<b>${member.displayName} (@${member.user.username})</b> ${verb} the Discord side of the chat`;

				try {
					// Send it
					await tgBot.sendMessage({
						text,
						chat_id: bridge.telegram.chatId,
						parse_mode: "HTML"
					});
				} catch (err) {
					Application.logger.error(`[${bridge.name}] Could not notify Telegram about a user that ${verb} Discord`, err);
				}
			});
	};
}

/**********************
 * The setup function *
 **********************/

/**
 * Sets up the receiving of Discord messages, and relaying them to Telegram
 *
 * @param {Discord.Client} dcBot	The Discord bot
 * @param {BotAPI} tgBot	The Telegram bot
 * @param {MessageMap} messageMap	Map between IDs of messages
 * @param {BridgeMap} bridgeMap	Map of the bridges to use
 * @param {Settings} settings	Settings to use
 */
function setup(dcBot, tgBot, messageMap, bridgeMap, settings) {
	// Create the map of latest message IDs and bridges
	const latestDiscordMessageIds = new LatestDiscordMessageIds("latestDiscordMessageIds.json");

	// Listen for users joining the server
	dcBot.on("guildMemberAdd", makeJoinLeaveFunc("joined", bridgeMap, tgBot));

	// Listen for users joining the server
	dcBot.on("guildMemberRemove", makeJoinLeaveFunc("left", bridgeMap, tgBot));

	// Listen for Discord messages
	dcBot.on("message", async (message) => {

		// Ignore the bot's own messages
		if (message.author.id === dcBot.user.id) {
			return;
		}

		// Check if this is a request for server info
		if (message.channel.type === "text" && message.cleanContent.toLowerCase() === `@${dcBot.user.username} chatinfo`.toLowerCase()) {
			// It is. Give it
			message.reply(
				"serverId: " + message.guild.id + "\n" +
				"channelId: " + message.channel.id + "\n"
			);

			// Don't process the message any further
			return;
		}

		// Get info about the sender
		const senderName = message.author.username + (settings.telegram.colonAfterSenderName ? ":" : "");

		// Check if the message is from the correct chat
		const bridge = bridgeMap.fromDiscordChannelId(message.channel.id);
		if (bridge !== undefined) {
			// Ignore it if this is a telegram-to-discord bridge
			if (bridge.direction === Bridge.DIRECTION_TELEGRAM_TO_DISCORD) {
				return;
			}

			// This is now the latest message for this bridge
			latestDiscordMessageIds.setLatest(message.id, bridge);

			// Check for attachments and pass them on
			message.attachments.forEach(async ({url}) => {
				try {
					await tgBot.sendMessage({
						chat_id: bridge.telegram.chatId,
						text: `<b>${senderName}</b>\n<a href="${url}">${url}</a>`,
						parse_mode: "HTML"
					});
				} catch (err) {
					Application.logger.error(`[${bridge.name}] Telegram did not accept an attachment:`, err);
				}
			});

			// Check the message for embeds
			message.embeds.forEach((embed) => {
				// Ignore it if it is not a "rich" embed (image, link, video, ...)
				if (embed.type !== "rich") {
					return;
				}

				// Convert it to something Telegram likes
				const text = handleEmbed(embed, senderName);

				try {
					// Send it
					tgBot.sendMessage({
						text,
						chat_id: bridge.telegram.chatId,
						parse_mode: "HTML",
						disable_web_page_preview: true
					});
				} catch (err) {
					Application.logger.error(`[${bridge.name}] Telegram did not accept an embed:`, err);
				}
			});

			// Check if there is an ordinary text message
			if (message.cleanContent) {

				// Modify the message to fit Telegram
				const processedMessage = md2html(message.cleanContent);

				// Pass the message on to Telegram
				try {
					const tgMessage = await tgBot.sendMessage({
						chat_id: bridge.telegram.chatId,
						text: `<b>${senderName}</b>\n${processedMessage}`,
						parse_mode: "HTML"
					});

					// Make the mapping so future edits can work
					messageMap.insert(MessageMap.DISCORD_TO_TELEGRAM, message.id, tgMessage.message_id);
				} catch (err) {
					Application.logger.error(`[${bridge.name}] Telegram did not accept a message:`, err);
					Application.logger.error(`[${bridge.name}] Failed message:`, err);
				}
			}

		} else if (message.channel.guild === undefined || !bridgeMap.knownDiscordServer(message.channel.guild.id)) {	// Check if it is the correct server
			// The message is from the wrong chat. Inform the sender that this is a private bot
			message.reply(
				"This is an instance of a TediCross bot, bridging a chat in Telegram with one in Discord. "
				+ "If you wish to use TediCross yourself, please download and create an instance. "
				+ "You may join our Discord server (https://discord.gg/MfzGMzy) "
				+ "or Telegram group (https://t.me/TediCrossSupport) for help. "
				+ "See also https://github.com/TediCross/TediCross"
			);
		}
	});

	// Listen for message edits
	dcBot.on("messageUpdate", async (oldMessage, newMessage) => {
		// Don't do anything with the bot's own messages
		if (newMessage.author.id === dcBot.user.id) {
			return;
		}

		// Ignore it if it's not from a known bridge
		const bridge = bridgeMap.fromDiscordChannelId(newMessage.channel.id);
		if (bridge === undefined) {
			return;
		}

		try {
			// Get the corresponding Telegram message ID
			const tgMessageId = await messageMap.getCorresponding(MessageMap.DISCORD_TO_TELEGRAM, newMessage.id);

			// Get info about the sender
			const senderName = newMessage.author.username + (settings.telegram.colonAfterSenderName ? ":" : "");

			// Modify the message to fit Telegram
			const processedMessage = md2html(newMessage.cleanContent);

			// Send the update to Telegram
			await tgBot.editMessageText({
				chat_id: bridge.telegram.chatId,
				message_id: tgMessageId,
				text: `<b>${senderName}</b>\n${processedMessage}`,
				parse_mode: "HTML"
			});
		} catch (err) {
			Application.logger.error(`[${bridge.name}] Could not edit Telegram message:`, err);
		}
	});

	// Start the Discord bot
	dcBot.login(settings.discord.token)
		// Complain if it could not authenticate for some reason
		.catch((err) => Application.logger.error("Could not authenticate the Discord bot:", err));

	// Listen for the 'disconnected' event
	dcBot.on("disconnected", async (evt) => {
		Application.logger.error("Discord bot disconnected!", evt);

		bridgeMap.bridges.forEach(async (bridge) => {
			try {
				await tgBot.sendMessage({
					chat_id: bridge.telegram.chatId,
					text: "**TEDICROSS**\nThe discord side of the bot disconnected! Please check the log"
				});
			} catch (err) {
				Application.logger.error(`[${bridge.name}] Could not send message to Telegram:`, err);
			}
		});
	});

	// Listen for errors
	dcBot.on("error", (err) => {
		if (err.code === "ECONNRESET") {
			// Lost connection to the discord servers
			Application.logger.warn("Lost connection to Discord's servers. The bot will resume when connection is reestablished, which should happen automatically. If it does not, please report this to the TediCross support channel");
		} else {
			// Unknown error. Tell the user to tell the devs
			Application.logger.error("The Discord bot ran into an error. Please post the following error message in the TediCross support channel");
			Application.logger.error(err);
		}
	});

	// Listen for debug messages
	if (settings.debug) {
		dcBot.on("debug", (str) => {
			Application.logger.log(str);
		});

		// Check the Discord bot's status every now and then
		setInterval(() => {
			if (dcBot.status !== Discord.Constants.Status.READY) {
				let actualStatus = null;
				switch (dcBot.status) {
					case Discord.Constants.Status.CONNECTING:
						actualStatus = "CONNECTING";
						break;
					case Discord.Constants.Status.RECONNECTING:
						actualStatus = "RECONNECTING";
						break;
					case Discord.Constants.Status.IDLE:
						actualStatus = "IDLE";
						break;
					case Discord.Constants.Status.NEARLY:
						actualStatus = "NEARLY";
						break;
					case Discord.Constants.Status.DISCONNETED:
						actualStatus = "DISCONNECTED";
						break;
					default:
						actualStatus = "UNKNOWN";
						break;
				}
				Application.logger.error(`Discord status not ready! Status is '${actualStatus}'`);
			}
		}, 1000);
	}

	// Make a promise which resolves when the dcBot is ready
	dcBot.ready = new Promise((resolve) => {
		// Listen for the 'ready' event
		dcBot.once("ready", () => {
			// Log the event
			Application.logger.info(`Discord: ${dcBot.user.username} (${dcBot.user.id})`);

			// Mark the bot as ready
			resolve();
		});
	});

	// Relay old messages, if wanted by the user
	if (!settings.discord.skipOldMessages) {
		dcBot.ready = relayOldMessages(dcBot, latestDiscordMessageIds, bridgeMap);
	}
}

/*****************************
 * Export the setup function *
 *****************************/

module.exports = setup;
<|endoftext|>


<commit_before>import path from 'path';
import SeleniumHelper from '../helpers/selenium-helper';

const {
    clickText,
    clickXpath,
    findByXpath,
    getDriver,
    getLogs,
    loadUri,
    scope
} = new SeleniumHelper();

const uri = path.resolve(__dirname, '../../build/index.html');

let driver;

describe('Loading scratch gui', () => {
    beforeAll(() => {
        driver = getDriver();
    });

    afterAll(async () => {
        await driver.quit();
    });

    describe('Loading projects by ID', () => {

        test('Nonexistent projects show error screen', async () => {
            await loadUri(`${uri}#999999999999999999999`);
            await clickText('Oops! Something went wrong.');
        });

        test('Load a project by ID directly through url', async () => {
            await driver.quit(); // Reset driver to test hitting # url directly
            driver = getDriver();

            const projectId = '96708228';
            await loadUri(`${uri}#${projectId}`);
            await clickXpath('//img[@title="Go"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Stop"]');
            const logs = await getLogs();
            await expect(logs).toEqual([]);
        });

        test('Load a project by ID (fullscreen)', async () => {
            await driver.quit(); // Reset driver to test hitting # url directly
            driver = getDriver();

            const prevSize = driver.manage()
                .window()
                .getSize();
            await new Promise(resolve => setTimeout(resolve, 2000));
            driver.manage()
                .window()
                .setSize(1920, 1080);
            const projectId = '96708228';
            await loadUri(`${uri}#${projectId}`);
            await clickXpath('//img[@title="Full Screen Control"]');
            await new Promise(resolve => setTimeout(resolve, 500));
            await clickXpath('//img[@title="Go"]');
            await new Promise(resolve => setTimeout(resolve, 1000));
            await clickXpath('//img[@title="Stop"]');
            prevSize.then(value => {
                driver.manage()
                    .window()
                    .setSize(value.width, value.height);
            });
            const logs = await getLogs();
            await expect(logs).toEqual([]);
        });

        test('Creating new project resets active tab to Code tab', async () => {
            await loadUri(uri);
            await new Promise(resolve => setTimeout(resolve, 2000));
            await findByXpath('//*[span[text()="Costumes"]]');
            await clickText('Costumes');
            await clickXpath(
                '//div[contains(@class, "menu-bar_menu-bar-item") and ' +
                'contains(@class, "menu-bar_hoverable")][span[text()="File"]]'
            );
            await clickXpath('//li[span[text()="New"]]');
            await findByXpath('//*[div[@class="scratchCategoryMenu"]]');
            await clickText('Operators', scope.blocksTab);
        });

        test('Not logged in->made no changes to project->create new project should not show alert', async () => {
            await loadUri(uri);
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath(
                '//div[contains(@class, "menu-bar_menu-bar-item") and ' +
                'contains(@class, "menu-bar_hoverable")][span[text()="File"]]'
            );
            await clickXpath('//li[span[text()="New"]]');
            await findByXpath('//*[div[@class="scratchCategoryMenu"]]');
            await clickText('Operators', scope.blocksTab);
        });

        test('Not logged in->made a change to project->create new project should show alert', async () => {
            await loadUri(uri);
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickText('Sounds');
            await clickXpath('//button[@aria-label="Choose a Sound"]');
            await clickText('A Bass', scope.modal); // Should close the modal
            await clickXpath(
                '//div[contains(@class, "menu-bar_menu-bar-item") and ' +
                'contains(@class, "menu-bar_hoverable")][span[text()="File"]]'
            );
            await clickXpath('//li[span[text()="New"]]');
            driver.switchTo()
                .alert()
                .accept();
            await findByXpath('//*[div[@class="scratchCategoryMenu"]]');
            await clickText('Operators', scope.blocksTab);
        });
    });
});
<commit_msg>fixed project loading test<commit_after>import path from 'path';
import SeleniumHelper from '../helpers/selenium-helper';

const {
    clickText,
    clickXpath,
    findByText,
    findByXpath,
    getDriver,
    getLogs,
    loadUri,
    scope
} = new SeleniumHelper();

const uri = path.resolve(__dirname, '../../build/index.html');

let driver;

describe('Loading scratch gui', () => {
    beforeAll(() => {
        driver = getDriver();
    });

    afterAll(async () => {
        await driver.quit();
    });

    describe('Loading projects by ID', () => {

        test('Nonexistent projects show error screen', async () => {
            await loadUri(`${uri}#999999999999999999999`);
            await clickText('Oops! Something went wrong.');
        });

        // skipping because it relies on network speed, and tests a method
        // of loading projects that we are not actively using anymore
        test.skip('Load a project by ID directly through url', async () => {
            await driver.quit(); // Reset driver to test hitting # url directly
            driver = getDriver();

            const projectId = '96708228';
            await loadUri(`${uri}#${projectId}`);
            await clickXpath('//img[@title="Go"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Stop"]');
            const logs = await getLogs();
            await expect(logs).toEqual([]);
        });

        // skipping because it relies on network speed, and tests a method
        // of loading projects that we are not actively using anymore
        test.skip('Load a project by ID (fullscreen)', async () => {
            await driver.quit(); // Reset driver to test hitting # url directly
            driver = getDriver();

            const prevSize = driver.manage()
                .window()
                .getSize();
            await new Promise(resolve => setTimeout(resolve, 2000));
            driver.manage()
                .window()
                .setSize(1920, 1080);
            const projectId = '96708228';
            await loadUri(`${uri}#${projectId}`);
            await clickXpath('//img[@title="Full Screen Control"]');
            await new Promise(resolve => setTimeout(resolve, 500));
            await clickXpath('//img[@title="Go"]');
            await new Promise(resolve => setTimeout(resolve, 1000));
            await clickXpath('//img[@title="Stop"]');
            prevSize.then(value => {
                driver.manage()
                    .window()
                    .setSize(value.width, value.height);
            });
            const logs = await getLogs();
            await expect(logs).toEqual([]);
        });

        test('Creating new project resets active tab to Code tab', async () => {
            await loadUri(uri);
            await findByXpath('//*[span[text()="Costumes"]]');
            await clickText('Costumes');
            await clickXpath(
                '//div[contains(@class, "menu-bar_menu-bar-item") and ' +
                'contains(@class, "menu-bar_hoverable")][span[text()="File"]]'
            );
            await clickXpath('//li[span[text()="New"]]');
            await findByXpath('//div[@class="scratchCategoryMenu"]');
            await clickText('Operators', scope.blocksTab);
        });

        test('Not logged in->made no changes to project->create new project should not show alert', async () => {
            await loadUri(uri);
            await clickXpath(
                '//div[contains(@class, "menu-bar_menu-bar-item") and ' +
                'contains(@class, "menu-bar_hoverable")][span[text()="File"]]'
            );
            await clickXpath('//li[span[text()="New"]]');
            await findByXpath('//*[div[@class="scratchCategoryMenu"]]');
            await clickText('Operators', scope.blocksTab);
        });

        test('Not logged in->made a change to project->create new project should show alert', async () => {
            await loadUri(uri);
            await clickText('Sounds');
            await clickXpath('//button[@aria-label="Choose a Sound"]');
            await clickText('A Bass', scope.modal); // Should close the modal
            await findByText('1.28'); // length of A Bass sound
            await clickXpath(
                '//div[contains(@class, "menu-bar_menu-bar-item") and ' +
                'contains(@class, "menu-bar_hoverable")][span[text()="File"]]'
            );
            await clickXpath('//li[span[text()="New"]]');
            driver.switchTo()
                .alert()
                .accept();
            await findByXpath('//*[div[@class="scratchCategoryMenu"]]');
            await clickText('Operators', scope.blocksTab);
        });
    });
});
<|endoftext|>


<commit_before>module("Discourse.ClickTrack", {
  setup: function() {

    // Prevent any of these tests from navigating away
    this.win = {focus: function() { } };
    this.redirectTo = sinon.stub(Discourse.URL, "redirectTo");
    sinon.stub(Discourse, "ajax");
    this.windowOpen = sinon.stub(window, "open").returns(this.win);
    sinon.stub(this.win, "focus");

    $('#qunit-fixture').html([
      '
',
      '
',
      '    google.com',
      '    google.com',
      '    google.com1',
      '    google.com1',
      '
',
      '      google.com1',
      '      google.com1',
      '
',
      '    forum',
      '    log.txt',
      '
',
      '
'].join("\n"));
  },

  teardown: function() {
    Discourse.URL.redirectTo.restore();
    Discourse.ajax.restore();
    window.open.restore();
    this.win.focus.restore();
  }
});

var track = Discourse.ClickTrack.trackClick;

// test
var generateClickEventOn = function(selector) {
  return $.Event("click", { currentTarget: $("#qunit-fixture " + selector)[0] });
};

test("does not track clicks on lightboxes", function() {
  var clickEvent = generateClickEventOn('.lightbox');
  this.stub(clickEvent, "preventDefault");
  ok(track(clickEvent));
  ok(!clickEvent.preventDefault.calledOnce);
});

test("it calls preventDefault when clicking on an a", function() {
  var clickEvent = generateClickEventOn('a');
  this.stub(clickEvent, "preventDefault");
  track(clickEvent);
  ok(clickEvent.preventDefault.calledOnce);
  ok(Discourse.URL.redirectTo.calledOnce);
});

test("does not track clicks on back buttons", function() {
  ok(track(generateClickEventOn('.back')));
});

test("does not track clicks on quote buttons", function() {
  ok(track(generateClickEventOn('.quote-other-topic')));
});

test("removes the href and put it as a data attribute", function() {
  track(generateClickEventOn('a'));

  var $link = $('#qunit-fixture a').first();
  ok($link.hasClass('no-href'));
  equal($link.data('href'), 'http://www.google.com');
  blank($link.attr('href'));
  ok($link.data('auto-route'));
  ok(Discourse.URL.redirectTo.calledOnce);
});

var badgeClickCount = function(id, expected) {
  track(generateClickEventOn('#' + id));
  var $badge = $('span.badge', $('#qunit-fixture #' + id).first());
  equal(parseInt($badge.html(), 10), expected);
};

test("does not update badge clicks on my own link", function() {
  this.stub(Discourse.User, 'currentProp').withArgs('id').returns(314);
  badgeClickCount('with-badge', 1);
});

test("does not update badge clicks in my own post", function() {
  this.stub(Discourse.User, 'currentProp').withArgs('id').returns(3141);
  badgeClickCount('with-badge-but-not-mine', 1);
});

test("updates badge counts correctly", function() {
  badgeClickCount('inside-onebox', 1);
  badgeClickCount('inside-onebox-forced', 2);
  badgeClickCount('with-badge', 2);
});

var trackRightClick = function() {
  var clickEvent = generateClickEventOn('a');
  clickEvent.which = 3;
  return track(clickEvent);
};

test("right clicks change the href", function() {
  ok(trackRightClick());
  equal($('#qunit-fixture a').first().prop('href'), "http://www.google.com/");
});

test("right clicks are tracked", function() {
  Discourse.SiteSettings.track_external_right_clicks = true;
  trackRightClick();
  equal($('#qunit-fixture a').first().attr('href'), "/clicks/track?url=http%3A%2F%2Fwww.google.com&post_id=42");
});

var expectToOpenInANewTab = function(clickEvent) {
  ok(!track(clickEvent));
  ok(Discourse.ajax.calledOnce);
  ok(window.open.calledOnce);
};

test("it opens in a new tab when pressing shift", function() {
  var clickEvent = generateClickEventOn('a');
  clickEvent.shiftKey = true;
  expectToOpenInANewTab(clickEvent);
});

test("it opens in a new tab when pressing meta", function() {
  var clickEvent = generateClickEventOn('a');
  clickEvent.metaKey = true;
  expectToOpenInANewTab(clickEvent);
});

test("it opens in a new tab when pressing meta", function() {
  var clickEvent = generateClickEventOn('a');
  clickEvent.ctrlKey = true;
  expectToOpenInANewTab(clickEvent);
});

test("it opens in a new tab when pressing meta", function() {
  var clickEvent = generateClickEventOn('a');
  clickEvent.which = 2;
  expectToOpenInANewTab(clickEvent);
});

test("tracks via AJAX if we're on the same site", function() {
  this.stub(Discourse.URL, "routeTo");
  this.stub(Discourse.URL, "origin").returns("http://discuss.domain.com");

  ok(!track(generateClickEventOn('#same-site')));
  ok(Discourse.ajax.calledOnce);
  ok(Discourse.URL.routeTo.calledOnce);
});

test("does not track via AJAX for attachments", function() {
  this.stub(Discourse.URL, "routeTo");
  this.stub(Discourse.URL, "origin").returns("http://discuss.domain.com");

  ok(!track(generateClickEventOn('.attachment')));
  ok(Discourse.URL.redirectTo.calledOnce);
});

test("tracks custom urls when opening in another window", function() {
  var clickEvent = generateClickEventOn('a');
  this.stub(Discourse.User, "currentProp").withArgs('external_links_in_new_tab').returns(true);
  ok(!track(clickEvent));
  ok(this.windowOpen.calledWith('/clicks/track?url=http%3A%2F%2Fwww.google.com&post_id=42', '_blank'));
});

test("tracks custom urls when opening in another window", function() {
  var clickEvent = generateClickEventOn('a');
  ok(!track(clickEvent));
  ok(this.redirectTo.calledWith('/clicks/track?url=http%3A%2F%2Fwww.google.com&post_id=42'));
});
<commit_msg>refactors click track tests to use fixture helper<commit_after>module("Discourse.ClickTrack", {
  setup: function() {

    // Prevent any of these tests from navigating away
    this.win = {focus: function() { } };
    this.redirectTo = sinon.stub(Discourse.URL, "redirectTo");
    sinon.stub(Discourse, "ajax");
    this.windowOpen = sinon.stub(window, "open").returns(this.win);
    sinon.stub(this.win, "focus");

    fixture().html([
      '
',
      '
',
      '    google.com',
      '    google.com',
      '    google.com1',
      '    google.com1',
      '
',
      '      google.com1',
      '      google.com1',
      '
',
      '    forum',
      '    log.txt',
      '
',
      '
'].join("\n"));
  },

  teardown: function() {
    Discourse.URL.redirectTo.restore();
    Discourse.ajax.restore();
    window.open.restore();
    this.win.focus.restore();
  }
});

var track = Discourse.ClickTrack.trackClick;

// test
var generateClickEventOn = function(selector) {
  return $.Event("click", { currentTarget: fixture(selector)[0] });
};

test("does not track clicks on lightboxes", function() {
  var clickEvent = generateClickEventOn('.lightbox');
  this.stub(clickEvent, "preventDefault");
  ok(track(clickEvent));
  ok(!clickEvent.preventDefault.calledOnce);
});

test("it calls preventDefault when clicking on an a", function() {
  var clickEvent = generateClickEventOn('a');
  this.stub(clickEvent, "preventDefault");
  track(clickEvent);
  ok(clickEvent.preventDefault.calledOnce);
  ok(Discourse.URL.redirectTo.calledOnce);
});

test("does not track clicks on back buttons", function() {
  ok(track(generateClickEventOn('.back')));
});

test("does not track clicks on quote buttons", function() {
  ok(track(generateClickEventOn('.quote-other-topic')));
});

test("removes the href and put it as a data attribute", function() {
  track(generateClickEventOn('a'));

  var $link = fixture('a').first();
  ok($link.hasClass('no-href'));
  equal($link.data('href'), 'http://www.google.com');
  blank($link.attr('href'));
  ok($link.data('auto-route'));
  ok(Discourse.URL.redirectTo.calledOnce);
});

var badgeClickCount = function(id, expected) {
  track(generateClickEventOn('#' + id));
  var $badge = $('span.badge', fixture('#' + id).first());
  equal(parseInt($badge.html(), 10), expected);
};

test("does not update badge clicks on my own link", function() {
  this.stub(Discourse.User, 'currentProp').withArgs('id').returns(314);
  badgeClickCount('with-badge', 1);
});

test("does not update badge clicks in my own post", function() {
  this.stub(Discourse.User, 'currentProp').withArgs('id').returns(3141);
  badgeClickCount('with-badge-but-not-mine', 1);
});

test("updates badge counts correctly", function() {
  badgeClickCount('inside-onebox', 1);
  badgeClickCount('inside-onebox-forced', 2);
  badgeClickCount('with-badge', 2);
});

var trackRightClick = function() {
  var clickEvent = generateClickEventOn('a');
  clickEvent.which = 3;
  return track(clickEvent);
};

test("right clicks change the href", function() {
  ok(trackRightClick());
  equal(fixture('a').first().prop('href'), "http://www.google.com/");
});

test("right clicks are tracked", function() {
  Discourse.SiteSettings.track_external_right_clicks = true;
  trackRightClick();
  equal(fixture('a').first().attr('href'), "/clicks/track?url=http%3A%2F%2Fwww.google.com&post_id=42");
});

var expectToOpenInANewTab = function(clickEvent) {
  ok(!track(clickEvent));
  ok(Discourse.ajax.calledOnce);
  ok(window.open.calledOnce);
};

test("it opens in a new tab when pressing shift", function() {
  var clickEvent = generateClickEventOn('a');
  clickEvent.shiftKey = true;
  expectToOpenInANewTab(clickEvent);
});

test("it opens in a new tab when pressing meta", function() {
  var clickEvent = generateClickEventOn('a');
  clickEvent.metaKey = true;
  expectToOpenInANewTab(clickEvent);
});

test("it opens in a new tab when pressing meta", function() {
  var clickEvent = generateClickEventOn('a');
  clickEvent.ctrlKey = true;
  expectToOpenInANewTab(clickEvent);
});

test("it opens in a new tab when pressing meta", function() {
  var clickEvent = generateClickEventOn('a');
  clickEvent.which = 2;
  expectToOpenInANewTab(clickEvent);
});

test("tracks via AJAX if we're on the same site", function() {
  this.stub(Discourse.URL, "routeTo");
  this.stub(Discourse.URL, "origin").returns("http://discuss.domain.com");

  ok(!track(generateClickEventOn('#same-site')));
  ok(Discourse.ajax.calledOnce);
  ok(Discourse.URL.routeTo.calledOnce);
});

test("does not track via AJAX for attachments", function() {
  this.stub(Discourse.URL, "routeTo");
  this.stub(Discourse.URL, "origin").returns("http://discuss.domain.com");

  ok(!track(generateClickEventOn('.attachment')));
  ok(Discourse.URL.redirectTo.calledOnce);
});

test("tracks custom urls when opening in another window", function() {
  var clickEvent = generateClickEventOn('a');
  this.stub(Discourse.User, "currentProp").withArgs('external_links_in_new_tab').returns(true);
  ok(!track(clickEvent));
  ok(this.windowOpen.calledWith('/clicks/track?url=http%3A%2F%2Fwww.google.com&post_id=42', '_blank'));
});

test("tracks custom urls when opening in another window", function() {
  var clickEvent = generateClickEventOn('a');
  ok(!track(clickEvent));
  ok(this.redirectTo.calledWith('/clicks/track?url=http%3A%2F%2Fwww.google.com&post_id=42'));
});
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
    _ = require('lodash'),
    User = mongoose.model('User'),
    UserEntry = mongoose.model('UserEntry');

var selectedFields = '-password -salt -provider -providerData -resetPasswordToken -resetPasswordExpires';

exports.listInvalid = function(req, res){
    User.find({isActive : false})
        .select(selectedFields)
        .exec(function(err, users){
            if(err) return res.status(400).send(err);
            res.json(users);
        });
};

exports.listBySearch = function(req, res){
    var query = req.body;

    User.find(query).select(selectedFields)
        .exec(function(err, users){
            if(err) return res.status(400).send(err);
            res.json(users);
        });
};
<commit_msg>Fix bugs<commit_after>'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
    _ = require('lodash'),
    User = mongoose.model('User'),
    UserEntry = mongoose.model('UserEntry');

var selectedFields = '-password -salt -provider -providerData -resetPasswordToken -resetPasswordExpires';

exports.listInvalid = function(req, res){
    User.find({$or : [{isActive : false},{verified:false}])
        .select(selectedFields)
        .exec(function(err, users){
            if(err) return res.status(400).send(err);
            res.json(users);
        });
};

exports.listBySearch = function(req, res){
    var query = req.body;
    query.isActive = true;
    query.verified = true;

    User.find(query).select(selectedFields)
        .exec(function(err, users){
            if(err) return res.status(400).send(err);
            res.json(users);
        });
};
<|endoftext|>


<commit_before>(function() {
  'use strict';

  Polymer({
    is: 'arc-menu-controller',
    properties: {
      route: String,
      baseUrl: String,
      _historyObserver: {
        type: Function,
        value: function() {
          return this._onStorageChange.bind(this);
        }
      },
      projects: Array,
      noHistory: {
        type: Boolean,
        value: false
      },
      // If set, the controller will use pouch db as a database connector and new database schema.
      usePouchDb: {
        type: Boolean
      },

      selectedProject: String
    },

    observers: [
      '_usePouchDbChanged(usePouchDb)',
      '_projectsChanged(projects.*)'
    ],

    _projectsChanged: function() {
      console.log('_projectsChanged', this.projects);
    },

    ready: function() {
      try {
        this._observeHistoryEnabled();
        this._updateHistoryStatus();
      } catch (e) {
        this.fire('app-log', {
          'message': ['Error occurred constructing the arc-menu', e],
          'level': 'error'
        });
        arc.app.analytics.sendException('arc-menu::ready::' + e.message, false);
      }
    },
    attached: function() {
      this.listen(document.body, 'project-removed', 'refreshProjects');
      this.listen(document, 'project-object-deleted', 'refreshProjects');
      this.listen(document, 'project-object-changed', '_updateProject');
      this.listen(document.body, 'project-name-changed', '_updateProjectName');
      this.listen(document, 'selected-project', '_updateProjectSelection');
    },
    detached: function() {
      this.unlisten(document.body, 'project-removed', 'refreshProjects');
      this.unlisten(document, 'project-object-deleted', 'refreshProjects');
      this.unlisten(document, 'project-object-changed', '_updateProject');
      this.unlisten(document.body, 'project-name-changed', '_updateProjectName');
      this.unlisten(document, 'selected-project', '_updateProjectSelection');
    },
    /**
     * User clicked on a navigation element.
     */
    _navigateRequested: function(e) {
      page(e.detail.url);
    },
    _usePouchDbChanged: function() {
      this.refreshProjects();
    },
    /**
     * Refresh projects list and display new list.
     */
    refreshProjects: function() {
      if (this.usePouchDb) {
        this._queryProjects();
      } else {
        this.$.model.query();
      }
    },

    _queryProjects: function() {
      let e = this.fire('arc-database-query', {
        store: 'legacy-projects',
        selector: '_id $exists true',
        sort: ['_id'],
        fields: ['_id', 'order', 'name']
      });
      e.detail.result
      .then((result) => {
        result.sort((a, b) => {
          if (a.order === b.order) {
            return 0;
          }
          if (a.order > b.order) {
            return 1;
          }
          if (a.order < b.order) {
            return -1;
          }
        });
        result = result.map((i) => {
          i.id = i._id;
          return i;
        });
        this.set('projects', result);
      })
      .catch((err) => {
        arc.app.analytics.sendException(err.message, true);
        this.fire('app-log', {'message': err, 'level': 'error'});
      });
    },

    /**
     * Attach listener to chrome local storage to listen for history settings change.
     */
    _observeHistoryEnabled: function() {
      try {
        chrome.storage.onChanged.addListener(this._historyObserver);
      } catch (e) {
        this.fire('app-log', {
          'message': ['Error setting up storage listener'. e],
          'level': 'error'
        });
        arc.app.analytics.sendException('arc-menu::ready::' + e.message, false);
      }
    },
    /**
     * Update project name in the UI.
     *
     * @param {Number} projectId A project id from the database
     * @param {String} projectName Project new name
     */
    updateProjectName: function(projectId, projectName) {
      if (this.project === null) {
        var msg = 'Trying to update a project name when project list is empty. ' +
          'Try insert new project first.';
        this.fire('app-log', {'message': msg, 'level': 'error'});
        return;
      }
      this.projects.forEach((project, i) => {
        if (project.id === projectId) {
          this.set('projects.' + i + '.name', projectName);
        }
      });
    },

    _updateProject: function(e, detail) {
      if (!this.projects || !this.projects.length) {
        return;
      }
      var index = this.projects.findIndex((i) => i._id === detail.project._id);
      if (index === -1) {
        return;
      }
      this.set('projects.' + index + '.name', detail.project.name);
      this.set('projects.' + index + '.order', detail.project.order);
    },

    /**
     * Add newly created project to the list.
     *
     * @param {Number} projectId Database id for the project
     */
    appendProject: function(/*projectId*/) {
      if (this.usePouchDb) {
        this._queryProjects();
      } else {
        this.$.model.query();
      }
    },
    /**
     * Remove project from the UI.
     */
    removeProject: function(projectId) {
      if (this.project === null) {
        var msg = 'Trying to remove a project when project list is empty. ' +
          'Try insert new project first.';
        this.fire('app-log', {'message': msg, 'level': 'warning'});
        return;
      }
      this.projects.forEach((project, i) => {
        if (project.id === projectId) {
          this.splice('projects', i, 1);
        }
      });
    },

    _updateHistoryStatus: function() {
      try {
        chrome.storage.sync.get({
          HISTORY_ENABLED: true
        }, function(result) {
          if (!result.HISTORY_ENABLED) {
            this.noHistory = true;
          } else {
            this.noHistory = false;
          }
        });
      } catch (e) {
        var msg = 'Error setting up storage listener';
        this.fire('app-log', {'message': [msg, e], 'level': 'warning'});
        arc.app.analytics.sendException('arc-menu::ready::' + e.message, false);
      }
    },

    _onStorageChange: function(change) {
      var keys = Object.keys(change);
      if (keys.indexOf('HISTORY_ENABLED') !== -1) {
        if (!change.HISTORY_ENABLED.newValue) {
          this.noHistory = true;
        } else {
          this.noHistory = false;
        }
      }
    },

    _authRequested: function() {
      this.$.auth.signIn();
    },

    _signOutRequested: function() {
      this.$.auth.signOut();
    },

    _updateProjectName: function(e) {
      var projectId = e.detail.projectId;
      var index = this.projects.findIndex((i) => i._id === projectId);
      if (index === -1) {
        return;
      }
      this.set('projects.' + index + '.name', e.detail.name);
    },

    _updateProjectSelection: function(e, detail) {
      this.set('selectedProject', detail.id);
    }
  });
})();
<commit_msg>Fix: Fixed problem with adding new project and not having proper ID set in the menu view<commit_after>(function() {
  'use strict';

  Polymer({
    is: 'arc-menu-controller',
    properties: {
      route: String,
      baseUrl: String,
      _historyObserver: {
        type: Function,
        value: function() {
          return this._onStorageChange.bind(this);
        }
      },
      projects: Array,
      noHistory: {
        type: Boolean,
        value: false
      },
      // If set, the controller will use pouch db as a database connector and new database schema.
      usePouchDb: {
        type: Boolean
      },

      selectedProject: String
    },

    observers: [
      '_usePouchDbChanged(usePouchDb)'
    ],

    ready: function() {
      try {
        this._observeHistoryEnabled();
        this._updateHistoryStatus();
      } catch (e) {
        this.fire('app-log', {
          'message': ['Error occurred constructing the arc-menu', e],
          'level': 'error'
        });
        arc.app.analytics.sendException('arc-menu::ready::' + e.message, false);
      }
    },
    attached: function() {
      this.listen(document.body, 'project-removed', 'refreshProjects');
      this.listen(document, 'project-object-deleted', 'refreshProjects');
      this.listen(document, 'project-object-changed', '_updateProject');
      this.listen(document.body, 'project-name-changed', '_updateProjectName');
      this.listen(document, 'selected-project', '_updateProjectSelection');
    },
    detached: function() {
      this.unlisten(document.body, 'project-removed', 'refreshProjects');
      this.unlisten(document, 'project-object-deleted', 'refreshProjects');
      this.unlisten(document, 'project-object-changed', '_updateProject');
      this.unlisten(document.body, 'project-name-changed', '_updateProjectName');
      this.unlisten(document, 'selected-project', '_updateProjectSelection');
    },
    /**
     * User clicked on a navigation element.
     */
    _navigateRequested: function(e) {
      page(e.detail.url);
    },
    _usePouchDbChanged: function() {
      this.refreshProjects();
    },
    /**
     * Refresh projects list and display new list.
     */
    refreshProjects: function() {
      if (this.usePouchDb) {
        this._queryProjects();
      } else {
        this.$.model.query();
      }
    },

    _queryProjects: function() {
      let e = this.fire('arc-database-query', {
        store: 'legacy-projects',
        selector: '_id $exists true',
        sort: ['_id'],
        fields: ['_id', 'order', 'name']
      });
      e.detail.result
      .then((result) => {
        result.sort((a, b) => {
          if (a.order === b.order) {
            return 0;
          }
          if (a.order > b.order) {
            return 1;
          }
          if (a.order < b.order) {
            return -1;
          }
        });
        result = result.map((i) => {
          i.id = i._id;
          return i;
        });
        this.set('projects', result);
      })
      .catch((err) => {
        arc.app.analytics.sendException(err.message, true);
        this.fire('app-log', {'message': err, 'level': 'error'});
      });
    },

    /**
     * Attach listener to chrome local storage to listen for history settings change.
     */
    _observeHistoryEnabled: function() {
      try {
        chrome.storage.onChanged.addListener(this._historyObserver);
      } catch (e) {
        this.fire('app-log', {
          'message': ['Error setting up storage listener'. e],
          'level': 'error'
        });
        arc.app.analytics.sendException('arc-menu::ready::' + e.message, false);
      }
    },
    /**
     * Update project name in the UI.
     *
     * @param {Number} projectId A project id from the database
     * @param {String} projectName Project new name
     */
    updateProjectName: function(projectId, projectName) {
      if (this.project === null) {
        var msg = 'Trying to update a project name when project list is empty. ' +
          'Try insert new project first.';
        this.fire('app-log', {'message': msg, 'level': 'error'});
        return;
      }
      this.projects.forEach((project, i) => {
        if (project.id === projectId) {
          this.set('projects.' + i + '.name', projectName);
        }
      });
    },

    _updateProject: function(e, detail) {
      var p = detail.project;
      p.id = p._id;
      if (!this.projects || !this.projects.length) {
        this.push('projects', p);
        return;
      }
      var index = this.projects.findIndex((i) => i._id === p._id);
      if (index === -1) {
        this.push('projects', p);
        return;
      }
      this.set('projects.' + index + '.name', p.name);
      this.set('projects.' + index + '.order', p.order);
    },

    /**
     * Add newly created project to the list.
     *
     * @param {Number} projectId Database id for the project
     */
    appendProject: function(/*projectId*/) {
      if (this.usePouchDb) {
        this._queryProjects();
      } else {
        this.$.model.query();
      }
    },
    /**
     * Remove project from the UI.
     */
    removeProject: function(projectId) {
      if (this.project === null) {
        var msg = 'Trying to remove a project when project list is empty. ' +
          'Try insert new project first.';
        this.fire('app-log', {'message': msg, 'level': 'warning'});
        return;
      }
      this.projects.forEach((project, i) => {
        if (project.id === projectId) {
          this.splice('projects', i, 1);
        }
      });
    },

    _updateHistoryStatus: function() {
      try {
        chrome.storage.sync.get({
          HISTORY_ENABLED: true
        }, function(result) {
          if (!result.HISTORY_ENABLED) {
            this.noHistory = true;
          } else {
            this.noHistory = false;
          }
        });
      } catch (e) {
        var msg = 'Error setting up storage listener';
        this.fire('app-log', {'message': [msg, e], 'level': 'warning'});
        arc.app.analytics.sendException('arc-menu::ready::' + e.message, false);
      }
    },

    _onStorageChange: function(change) {
      var keys = Object.keys(change);
      if (keys.indexOf('HISTORY_ENABLED') !== -1) {
        if (!change.HISTORY_ENABLED.newValue) {
          this.noHistory = true;
        } else {
          this.noHistory = false;
        }
      }
    },

    _authRequested: function() {
      this.$.auth.signIn();
    },

    _signOutRequested: function() {
      this.$.auth.signOut();
    },

    _updateProjectName: function(e) {
      var projectId = e.detail.projectId;
      var index = this.projects.findIndex((i) => i._id === projectId);
      if (index === -1) {
        return;
      }
      this.set('projects.' + index + '.name', e.detail.name);
    },

    _updateProjectSelection: function(e, detail) {
      this.set('selectedProject', detail.id);
    }
  });
})();
<|endoftext|>


<commit_before>"use strict";

module.exports = ($scope, BMA, UIUtils, summary, bmapi, ws) => {

  UIUtils.enableTabs();

  let co = require('co');

  bindBlockWS();
  $scope.current = summary.current;
  $scope.server_started = true;
  $scope.server_stopped = false;
  $scope.phones = [];
  $scope.abc = 'abcdef';
  $scope.newIdentities = 2;

  $(".dropdown-button").dropdown({ constrainwidth: false });

  ws.on(undefined, (data) => {
    if (data.type === 'started') {
      $scope.server_started = true;
      $scope.server_stopped = false;
      bindBlockWS();
      UIUtils.toast('general.server.started');
      $scope.$apply();
    }
    if (data.type === 'stopped') {
      $scope.server_stopped = true;
      $scope.server_started = false;
      UIUtils.toast('general.server.stopped');
      $scope.$apply();
    }
  });

  function bindBlockWS() {
    bmapi.websocket.block().on(undefined, (block) => {
      $scope.current = block;
      $scope.$apply();
    });
  }

  $scope.reconfigure_network = () => co(function *() {
    $scope.reconfiguring = true;
    let delay = Q.delay(1000);
    try {
      let netinferfaces = yield BMA.webmin.network.interfaces();
      let conf = {};
      conf.local_ipv4 = netinferfaces.auto.local.ipv4 || '';
      conf.local_ipv6 = netinferfaces.auto.local.ipv6 || '';
      conf.remote_ipv4 = netinferfaces.auto.remote.ipv4 || '';
      conf.remote_ipv6 = netinferfaces.auto.remote.ipv6 || '';
      conf.lport = netinferfaces.auto.local.port || 9330;
      conf.rport = netinferfaces.auto.remote.port || 9330;
      conf.upnp = netinferfaces.auto.remote.upnp || false;
      conf.dns = netinferfaces.auto.remote.dns || '';
      yield BMA.webmin.server.netConf({
        conf: conf
      });
      yield delay;
      $scope.should_reconfigure = false;
      UIUtils.toast('general.network.reconf_ok');
      $scope.$apply();
    } catch (e) {
      yield delay;
      $scope.reconfiguring = false;
      $scope.$apply();
    }
  });

  return co(function *() {
    yield $scope.startServer();
    try {
      yield bmapi.network.peering.self();
    } catch (e) {
      $scope.should_reconfigure = true;
    }
  });
};
<commit_msg>Fix: program was stopped after few seconds when UPnP was no more available<commit_after>"use strict";

module.exports = ($scope, BMA, UIUtils, summary, bmapi, ws) => {

  UIUtils.enableTabs();

  let co = require('co');

  bindBlockWS();
  $scope.current = summary.current;
  $scope.server_started = true;
  $scope.server_stopped = false;
  $scope.phones = [];
  $scope.abc = 'abcdef';
  $scope.newIdentities = 2;

  $(".dropdown-button").dropdown({ constrainwidth: false });

  ws.on(undefined, (data) => {
    if (data.type === 'started') {
      $scope.server_started = true;
      $scope.server_stopped = false;
      bindBlockWS();
      UIUtils.toast('general.server.started');
      $scope.$apply();
    }
    if (data.type === 'stopped') {
      $scope.server_stopped = true;
      $scope.server_started = false;
      UIUtils.toast('general.server.stopped');
      $scope.$apply();
    }
  });

  function bindBlockWS() {
    bmapi.websocket.block().on(undefined, (block) => {
      $scope.current = block;
      $scope.$apply();
    });
  }

  $scope.reconfigure_network = () => co(function *() {
    $scope.reconfiguring = true;
    let delay = Q.delay(1000);
    try {
      let netinferfaces = yield BMA.webmin.network.interfaces();
      let conf = {};
      conf.local_ipv4 = netinferfaces.auto.local.ipv4 || '';
      conf.local_ipv6 = netinferfaces.auto.local.ipv6 || '';
      conf.remote_ipv4 = netinferfaces.auto.remote.ipv4 || '';
      conf.remote_ipv6 = netinferfaces.auto.remote.ipv6 || '';
      conf.lport = netinferfaces.auto.local.port || 9330;
      conf.rport = netinferfaces.auto.remote.port || 9330;
      conf.upnp = netinferfaces.auto.remote.upnp || false;
      conf.dns = netinferfaces.auto.remote.dns || '';
      yield BMA.webmin.server.netConf({
        conf: conf
      });
      yield delay;
      $scope.should_reconfigure = false;
      UIUtils.toast('general.network.reconf_ok');
      $scope.$apply();
    } catch (e) {
      yield delay;
      $scope.reconfiguring = false;
      $scope.$apply();
    }
  });

  return co(function *() {
    yield $scope.startServer();
    try {
      yield bmapi.network.peering.self();
    } catch (e) {
      console.log(e);
      $scope.should_reconfigure = true;
    }
  });
};
<|endoftext|>


<commit_before>/*
 * TimeCard View
 * Copyright ©2015 Thomas Nelson, Jacob Nichols, David Opp, Todd Brochu,
Andrew McGown, Sasha Fahrenkopf, Cameron B. White.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE text file in the root directory of this source tree.
 */
import React from "react"
import Router, { RouteHandler, Link} from "react-router"
import _ from "lodash"
import moment from "moment"
import FluxComponent from 'flummox/component';
import flux from "../../flux/flux"

export default class PayPeriodStats extends React.Component {
  render() {
    const { timesheet } = this.props
    const totals = timesheet.totals

    const allPaidHours = _.chain(totals)
        .find(each => each.payCodeId == "142")
        .get('amountInTime', 0)
        .value()
    const allOvertime = _.chain(totals)
        .find(each => each.payCodeId == "141")
        .get('amountInTime', 0)
        .value()
    const allPTO = _.chain(totals)
        .find(each => each.payCodeId == "501")
        .get('amountInTime', 0)
        .value()

    return (

Hours Worked

{allPaidHours}

OT Hours

{allOvertime}

PTO Hours

{allPTO}

    );
  }
}

class HoursPTO extends React.Component {
  // Returns the number of PTO hours used in the pay period
  render() {
    // TODO: Replace with appropriate API result
    const {AmountInTime, AmountInCurrency} = this.props.allPTO
    return (

{AmountInTime}

PTO

    );
  }
}

class LatePunches extends React.Component {
  // Returns the number times the pay period has late punches
  render() {
    const { Timesheet } = this.props
    const timesLate = _.chain(Timesheet)
        .get('TotaledSpans.TotaledSpan', {})
        .pluck('Exceptions')
        .compact()
        .pluck('TimekeepingException')
        .map(each => {
          if (_.isObject(each) && each.ExceptionTypeName == "LATE") {
            return 1
          } else {
            return _.chain(each)
             .filter(each => each.ExceptionTypeName == "LATE")
             .value()
             .length
          }
        })
        .reduce((memo, each) => memo + each)
        .value()
    return (

{timesLate}

Late Punches

    );
  }
}
// <commit_msg>Switched PTO and OT in payperiodoverview's header<commit_after>/*
 * TimeCard View
 * Copyright ©2015 Thomas Nelson, Jacob Nichols, David Opp, Todd Brochu,
Andrew McGown, Sasha Fahrenkopf, Cameron B. White.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE text file in the root directory of this source tree.
 */
import React from "react"
import Router, { RouteHandler, Link} from "react-router"
import _ from "lodash"
import moment from "moment"
import FluxComponent from 'flummox/component';
import flux from "../../flux/flux"

export default class PayPeriodStats extends React.Component {
  render() {
    const { timesheet } = this.props
    const totals = timesheet.totals

    const allPaidHours = _.chain(totals)
        .find(each => each.payCodeId == "142")
        .get('amountInTime', 0)
        .value()
    const allOvertime = _.chain(totals)
        .find(each => each.payCodeId == "141")
        .get('amountInTime', 0)
        .value()
    const allPTO = _.chain(totals)
        .find(each => each.payCodeId == "501")
        .get('amountInTime', 0)
        .value()

    return (

Hours Worked

{allPaidHours}

PTO Hours

{allPTO}

OT Hours

{allOvertime}

    );
  }
}

class HoursPTO extends React.Component {
  // Returns the number of PTO hours used in the pay period
  render() {
    // TODO: Replace with appropriate API result
    const {AmountInTime, AmountInCurrency} = this.props.allPTO
    return (

{AmountInTime}

PTO

    );
  }
}

class LatePunches extends React.Component {
  // Returns the number times the pay period has late punches
  render() {
    const { Timesheet } = this.props
    const timesLate = _.chain(Timesheet)
        .get('TotaledSpans.TotaledSpan', {})
        .pluck('Exceptions')
        .compact()
        .pluck('TimekeepingException')
        .map(each => {
          if (_.isObject(each) && each.ExceptionTypeName == "LATE") {
            return 1
          } else {
            return _.chain(each)
             .filter(each => each.ExceptionTypeName == "LATE")
             .value()
             .length
          }
        })
        .reduce((memo, each) => memo + each)
        .value()
    return (

{timesLate}

Late Punches

    );
  }
}
// <|endoftext|>