<commit_before>var Telegram = require('node-telegram-bot-api');
var fs = require('fs');
var path = require('path');
var irc = require('./irc');
var nodeStatic = require('node-static');
var mkdirp = require('mkdirp');

// tries to read chat ids from a file
var readChatIds = function(arr) {
    console.log('\n');
    console.log('NOTE!');
    console.log('=====');

    var idMissing = false;
    try {
        var json = JSON.parse(fs.readFileSync(process.env.HOME + '/.teleirc/chat_ids'));
        for (var i = 0; i < arr.length; i++) {
            var key = arr[i].tgGroup;
            if (key in json) {
                arr[i].tgChatId = json[key];
                console.log('id found for:', key, ':', json[key]);
            } else {
                console.log('id not found:', key);
                idMissing = true;
            }
        }
    } catch (e) {
        console.log('~/.teleirc/chat_ids file not found!');
        idMissing = true;
    }

    if (idMissing) {
        console.log(
            '\nPlease add your Telegram bot to a Telegram group and have' +
            '\nsomeone send a message to that group.' +
            '\nteleirc will then automatically store your group chat_id.');
    }

    console.log('\n');
};

var writeChatIds = function(config) {
    var json = {};
    for (var i = 0; i < config.channels.length; i++) {
        if (config.channels[i].tgChatId) {
            json[config.channels[i].tgGroup] = config.channels[i].tgChatId;
        }
    }
    json = JSON.stringify(json);
    fs.writeFile(process.env.HOME + '/.teleirc/chat_ids', json, function(err) {
        if (err) {
            console.log('error while storing chat ID:');
            console.log(err);
        } else {
            console.log('successfully stored chat ID in ~/.teleirc/chat_ids');
        }
    });
};

var getName = function(user, config) {
    var name = config.nameFormat;

    if (user.username) {
        name = name.replace('%username%', user.username, 'g');
    } else {
        // if user lacks username, use fallback format string instead
        name = name.replace('%username%', config.usernameFallbackFormat, 'g');
    }

    name = name.replace('%firstName%', user.first_name, 'g');
    name = name.replace('%lastName%', user.last_name, 'g');

    return name;
};

var serveFile = function(fileId, config, tg, callback) {
    tg.downloadFile(fileId, process.env.HOME + '/.teleirc/files').then(function(filePath) {
        callback(config.httpLocation + '/' + path.basename(filePath));
    });
};

module.exports = function(config, sendTo) {
    // start HTTP server for media files if configured to do so
    if (config.showMedia) {
        var fileServer = new nodeStatic.Server(process.env.HOME + '/.teleirc/files');
        mkdirp(process.env.HOME + '/.teleirc/files');

        require('http').createServer(function(req, res) {
            req.addListener('end', function() {
                fileServer.serve(req, res);
            }).resume();
        }).listen(config.httpPort);
    }

    var tg = new Telegram(config.tgToken, {polling: true});

    readChatIds(config.channels);

    tg.on('message', function(msg) {
        var channel = config.channels.filter(function(channel) {
            return channel.tgGroup === msg.chat.title;
        })[0];

        if (!channel) {
            return;
        }

        if (!channel.tgChatId) {
            console.log('storing chat ID: ' + msg.chat.id);
            channel.tgChatId = msg.chat.id;
            writeChatIds(config);
        }

        // skip posts containing media if it's configured off
        if ((msg.audio || msg.document || msg.photo || msg.sticker || msg.video ||
            msg.voice || msg.contact || msg.location) && !config.showMedia) {
            return;
        }

        if (msg.reply_to_message && msg.text) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '@' + getName(msg.reply_to_message.from, config) + ', ' + msg.text);
        } else if (msg.audio) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '(Audio)');
        } else if (msg.document) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '(Document)');
        } else if (msg.photo) {
            // pick the highest quality photo
            var photo = msg.photo[msg.photo.length - 1];

            serveFile(photo.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(Photo, ' + photo.width + 'x' + photo.height + ') ' + url);
            });
        } else if (msg.sticker) {
            serveFile(msg.sticker.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(Sticker, ' + msg.sticker.width + 'x' + msg.sticker.height + ') ' + url);
            });
        } else if (msg.video) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '(Video, ' + msg.video.duration + 's)');
        } else if (msg.voice) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '(Voice, ' + msg.audio.duration + 's)');
        } else if (msg.contact) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '(Contact, ' + '"' + msg.contact.first_name + ' ' +
                msg.contact.last_name + '", ' +
                msg.contact.phone_number + ')');
        } else if (msg.location) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '(Location, ' + 'lon: ' + msg.location.longitude +
                              ', lat: ' + msg.location.latitude + ')');
        } else if (msg.new_chat_participant) {
            sendTo.irc(channel.ircChan, getName(msg.new_chat_participant, config) +
                ' was added by: ' + getName(msg.from, config));
        } else if (msg.left_chat_participant) {
            sendTo.irc(channel.ircChan, getName(msg.left_chat_participant, config) +
                ' was removed by: ' + getName(msg.from, config));
        } else {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' + msg.text);
        }
    });

    sendTo.tg = function(channel, msg) {
        console.log('  >> relaying to TG: ' + msg);

        if (!channel.tgChatId) {
            var err = 'ERROR: No chat_id set! Add me to a Telegram group ' +
                      'and say hi so I can find your group\'s chat_id!';
            sendTo.irc(channel.ircChan, err);
            console.error(err);
            return;
        }

        tg.sendMessage(channel.tgChatId, msg);
    };
};
<commit_msg>don't print undefined if last name isn't defined, get rid of leading/trailing ws<commit_after>var Telegram = require('node-telegram-bot-api');
var fs = require('fs');
var path = require('path');
var irc = require('./irc');
var nodeStatic = require('node-static');
var mkdirp = require('mkdirp');

// tries to read chat ids from a file
var readChatIds = function(arr) {
    console.log('\n');
    console.log('NOTE!');
    console.log('=====');

    var idMissing = false;
    try {
        var json = JSON.parse(fs.readFileSync(process.env.HOME + '/.teleirc/chat_ids'));
        for (var i = 0; i < arr.length; i++) {
            var key = arr[i].tgGroup;
            if (key in json) {
                arr[i].tgChatId = json[key];
                console.log('id found for:', key, ':', json[key]);
            } else {
                console.log('id not found:', key);
                idMissing = true;
            }
        }
    } catch (e) {
        console.log('~/.teleirc/chat_ids file not found!');
        idMissing = true;
    }

    if (idMissing) {
        console.log(
            '\nPlease add your Telegram bot to a Telegram group and have' +
            '\nsomeone send a message to that group.' +
            '\nteleirc will then automatically store your group chat_id.');
    }

    console.log('\n');
};

var writeChatIds = function(config) {
    var json = {};
    for (var i = 0; i < config.channels.length; i++) {
        if (config.channels[i].tgChatId) {
            json[config.channels[i].tgGroup] = config.channels[i].tgChatId;
        }
    }
    json = JSON.stringify(json);
    fs.writeFile(process.env.HOME + '/.teleirc/chat_ids', json, function(err) {
        if (err) {
            console.log('error while storing chat ID:');
            console.log(err);
        } else {
            console.log('successfully stored chat ID in ~/.teleirc/chat_ids');
        }
    });
};

var getName = function(user, config) {
    var name = config.nameFormat;

    if (user.username) {
        name = name.replace('%username%', user.username, 'g');
    } else {
        // if user lacks username, use fallback format string instead
        name = name.replace('%username%', config.usernameFallbackFormat, 'g');
    }

    name = name.replace('%firstName%', user.first_name || '', 'g');
    name = name.replace('%lastName%', user.last_name || '', 'g');

    // get rid of leading and trailing whitespace
    name = name.replace(/(^\s*)|(\s*$)/g, '');

    return name;
};

var serveFile = function(fileId, config, tg, callback) {
    tg.downloadFile(fileId, process.env.HOME + '/.teleirc/files').then(function(filePath) {
        callback(config.httpLocation + '/' + path.basename(filePath));
    });
};

module.exports = function(config, sendTo) {
    // start HTTP server for media files if configured to do so
    if (config.showMedia) {
        var fileServer = new nodeStatic.Server(process.env.HOME + '/.teleirc/files');
        mkdirp(process.env.HOME + '/.teleirc/files');

        require('http').createServer(function(req, res) {
            req.addListener('end', function() {
                fileServer.serve(req, res);
            }).resume();
        }).listen(config.httpPort);
    }

    var tg = new Telegram(config.tgToken, {polling: true});

    readChatIds(config.channels);

    tg.on('message', function(msg) {
        var channel = config.channels.filter(function(channel) {
            return channel.tgGroup === msg.chat.title;
        })[0];

        if (!channel) {
            return;
        }

        if (!channel.tgChatId) {
            console.log('storing chat ID: ' + msg.chat.id);
            channel.tgChatId = msg.chat.id;
            writeChatIds(config);
        }

        // skip posts containing media if it's configured off
        if ((msg.audio || msg.document || msg.photo || msg.sticker || msg.video ||
            msg.voice || msg.contact || msg.location) && !config.showMedia) {
            return;
        }

        if (msg.reply_to_message && msg.text) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '@' + getName(msg.reply_to_message.from, config) + ', ' + msg.text);
        } else if (msg.audio) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '(Audio)');
        } else if (msg.document) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '(Document)');
        } else if (msg.photo) {
            // pick the highest quality photo
            var photo = msg.photo[msg.photo.length - 1];

            serveFile(photo.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(Photo, ' + photo.width + 'x' + photo.height + ') ' + url);
            });
        } else if (msg.sticker) {
            serveFile(msg.sticker.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(Sticker, ' + msg.sticker.width + 'x' + msg.sticker.height + ') ' + url);
            });
        } else if (msg.video) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '(Video, ' + msg.video.duration + 's)');
        } else if (msg.voice) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '(Voice, ' + msg.audio.duration + 's)');
        } else if (msg.contact) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '(Contact, ' + '"' + msg.contact.first_name + ' ' +
                msg.contact.last_name + '", ' +
                msg.contact.phone_number + ')');
        } else if (msg.location) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '(Location, ' + 'lon: ' + msg.location.longitude +
                              ', lat: ' + msg.location.latitude + ')');
        } else if (msg.new_chat_participant) {
            sendTo.irc(channel.ircChan, getName(msg.new_chat_participant, config) +
                ' was added by: ' + getName(msg.from, config));
        } else if (msg.left_chat_participant) {
            sendTo.irc(channel.ircChan, getName(msg.left_chat_participant, config) +
                ' was removed by: ' + getName(msg.from, config));
        } else {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' + msg.text);
        }
    });

    sendTo.tg = function(channel, msg) {
        console.log('  >> relaying to TG: ' + msg);

        if (!channel.tgChatId) {
            var err = 'ERROR: No chat_id set! Add me to a Telegram group ' +
                      'and say hi so I can find your group\'s chat_id!';
            sendTo.irc(channel.ircChan, err);
            console.error(err);
            return;
        }

        tg.sendMessage(channel.tgChatId, msg);
    };
};
<|endoftext|>


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

  // Store a local reference to jQuery and Underscorea.
  var $ = window.jQuery;
  var _ = window._;

  var keyDirMap = {'38': 'prev', '40': 'next'};
  var ctrlKeyDirMap = {'80': 'prev', '78': 'next'};

  // Delay a callback `n` ms or invoke immediately if 0.
  var delay = function (cb, n) { return !n ? cb() : _.delay(cb, n); };

  // Direct event bindings to `$el`.
  var directEvents = {
    mouseover: function () { this._hover = true; },
    mouseout: function () {
      this._hover = false;
      _.defer(_.bind(function () {
        if (!this._focus && !this._hover) this.hide();
      }, this));
    }
  };

  // Delegated event bindings to children of `$el`.
  var delegatedEvents = {

    // Delegated events for the input area.
    '.js-rec-input': {

      // Update the `_focus` flag and show the latest query results.
      focus: function (ev) {
        this._focus = true;
        this.query($(ev.currentTarget).val()).show();
      },

      // Check for the up/down keys and enter key, and finally submit a new
      // query.
      keydown: function (ev) {
        var key = ev.which;
        var dir = keyDirMap[key] || (ev.ctrlKey && ctrlKeyDirMap[key]);
        if (dir) {
          ev.preventDefault();
          return this[dir]();
        }
        if (key === 13) {
          var $selected = this.$el.find('.js-rec-selected');
          if ($selected.length) {
            $selected[0].click();
            return ev.preventDefault();
          }
        }
        var $input = $(ev.currentTarget);
        _.defer(_.bind(function () { this.query($input.val()); }, this));
      },

      // Hide the results when focus is lost.
      blur: function () {
        this._focus = false;
        if (!this._hover) this.hide();
      }
    },

    // Delegated events for invidiual results.
    '.js-rec-result': {

      // Select the result that is being hovered over.
      mouseover: function (ev) { this.select($(ev.currentTarget)); },

      // Optionally clear and hide the input and results on result click.
      click: function () {
        var $input = this.$el.find('.js-rec-input');
        if (this.clearOnClick) $input.val('').focus();
        if (this.hideOnClick) {
          $input.blur();
          this.hide();
        }
      }
    }
  };

  // Create the `Rec` constructor.
  //
  // ```js
  // var rec = new Rec('#my-rec-container', {
  //   url: '/search',
  //   template: jst['search-result']
  // });
  // ```
  var Rec = window.Rec = function (el, options) {

    // Define direct and delegated event hashes unique to this instance. This
    // will allow a clean unbinding of these events without worry about any
    // event collisions.
    this._directEvents = {};
    _.each(directEvents, function (cb, key) {
      this._directEvents[key] = _.bind(cb, this);
    }, this);
    this._delegatedEvents = {};
    _.each(delegatedEvents, function (evs, selector) {
      var bound = this._delegatedEvents[selector] = {};
      _.each(evs, function (cb, name) {
        bound[name] = _.bind(cb, this);
      }, this);
    }, this);

    // Create a cache object to store results.
    this._cached = {};

    // Track concurrent fetches for showing/hiding the loading spinner.
    this._fetchQueue = 0;

    // Set the container element.
    this.setElement(el);

    // Extend the instance with its options.
    for (var name in options) this[name] = options[name];
  };

  // Define `prototype` properties and methods for `Olay`.
  var proto = {

    // How long since the last keystroke should rec wait to call `fetch`?
    delay: 250,

    // How many results should be displayed? `0` means as many as are returned
    // by `fetch`.
    limit: 0,

    // Should results be cached? Any cross-session caching will need to be
    // handled by your `fetch` method, but in-memory caching is free with rec.
    cache: true,

    // Should the first result automatically be selected? In other words, will
    // hitting return fire the first recommended result or the broad result?
    selectFirst: true,

    // Should the results be hidden and focus blurred when a result is clicked?
    hideOnClick: false,

    // Should the input be cleared when a result is clicked?
    clearOnClick: true,

    // The query string key that will be used with the default `fetch` method.
    queryKey: 'q',

    // Override this template functions to return your labels and results the
    // way you want them. If you are not using the `groupBy` option,
    // `labelTemplate` will be ignored.
    labelTemplate: function (label) { return '
' + label + '
'; },
    resultTemplate: function (result) { return '
' + result + '
'; },

    // Parse the query before sending it to `fetch`. This allows you to clean up
    // bad characters, extra spaces, etc...
    parse: function (q) {
      return q.replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, '').toLowerCase();
    },

    // `filter` works alongside `cache` to intelligently predict possible
    // results while waiting for a server request to come back. Override this
    // function with logic that best matches your server's filter logic.
    filter: function (q, result) {
      var str = (result.title || '').toLowerCase();
      return _.every(q.split(' '), function (word) {
        return ~str.indexOf(word);
      });
    },

    // The default `fetch` is designed for simple, JSON AJAX requests. Override
    // thid function to suite your application's needs.
    fetch: function (q, cb) {
      var options = this.fetchOptions || {};
      (options.data || (options.data = {}))[this.queryKey] = q;
      $.ajax(_.extend({
        type: 'get',
        dataType: 'json',
        success: function (results) { cb(null, results); },
        error: function (xhr) { cb(xhr.responseText); }
      }, options));
      return this;
    },

    query: function (q) {

      // Parse the raw query string.
      q = this.parse(q);

      // Compare to the last query and store so we don't waste any time.
      if (q === this._lastQ) return this;
      this._lastQ = q;

      // Clear the old `fetch` request if one was about to fire.
      if (this._timeout) {
        clearTimeout(this._timeout);
        this._timeout = null;
        if (!--this._fetchQueue) this.$el.removeClass('js-rec-loading');
      }

      // Looked for a cache result before trying to load.
      if (!this._getCached(q)) {

        // Add the loading class.
        ++this._fetchQueue;
        this.$el.addClass('js-rec-loading');

        // Start a timeout to `fetch`
        var self = this;
        this._timeout = delay(function () {
          self._timeout = null;
          self.fetch(q, function (er, results) {
            if (!--self._fetchQueue) self.$el.removeClass('js-rec-loading');
            if (!er) self._setCached(q, results);
            self._render();
          });
        }, this.delay);
      }
      return this._render();
    },

    // Set the input element the rec will watch.
    setElement: function (el) {
      if (this.$el) this._unbind();
      this.$el = el instanceof $ ? el : $(el);
      return this._bind();
    },

    show: function () {
      this.$el.find('.js-rec-results').removeAttr('style');
      this.select();
      return this;
    },

    hide: function () {
      this.$el.find('.js-rec-results').css('display', 'none');
      return this;
    },

    select: function ($el) {
      this.$el.find('.js-rec-selected').removeClass('js-rec-selected');
      if (!$el && this.selectFirst) {
        $el = this.$el.find('.js-rec-result').first();
      }
      if ($el) $el.addClass('js-rec-selected');
      return this;
    },

    prev: function () {
      return this.select(
        this.$el.find('.js-rec-selected').prevAll('.js-rec-result').first()
      );
    },

    next: function () {
      var $selected = this.$el.find('.js-rec-selected');
      var $next = $selected.nextAll('.js-rec-result').first();
      if (!$selected.length) {
        $next = this.$el.find('.js-rec-result').first();
      } else if (!$next.length) {
        return this;
      }
      return this.select($next);
    },

    _getCached: function (q) {
      return this.cache && this._cached[q];
    },

    _setCached: function (q, results) {
      results = results instanceof Array ? results : [];
      this._cached[q] = _.groupBy(results, this.groupBy || 'undefined');
      return this;
    },

    _getFiltered: function (q) {
      if (!this.filter) return null;
      var cached;
      for (var i = q.length - 1; i > 0; --i) {
        if (cached = this._getCached(q.slice(0, i))) break;
      }
      if (!cached) return null;
      var filter = _.bind(this.filter, this, q);
      var matches = _.reduce(cached, function (matches, results, label) {
        var filtered = _.filter(results, filter);
        if (filtered.length) matches[label] = filtered;
        return matches;
      }, {});
      return _.size(matches) ? matches : null;
    },

    // Bind (or unbind) delegated events from a multi-level object.
    _bind: function (unbind) {
      var $el = this.$el;
      var method = unbind ? 'off': 'on';
      $el[method](this._directEvents);
      _.each(this._delegatedEvents, function (evs, selector) {
        _.each(evs, function (cb, name) { $el[method](name, selector, cb); });
      });
      return this;
    },

    // Inverse `_bind`.
    _unbind: function () { return this._bind('off'); },

    // Build the elements for the most recent query.
    _render: function () {
      var q = this._lastQ;
      var results = this._getCached(q) || this._getFiltered(q);
      this.$el.removeClass('js-rec-no-results');
      this.$el.find('.js-rec-result, .js-rec-label').remove();
      var $results = this.$el.find('.js-rec-results');
      if (!results) return this;
      if (_.size(results)) {
        var limit = this.limit;
        var count = 0;
        _.each(results, function (results, label) {
          if (limit && count === limit) return;
          if (label !== 'undefined') $results.append(this._renderLabel(label));
          _.each(results, function (result) {
            if (limit && count === limit) return;
            $results.append(this._renderResult(result));
            ++count;
          }, this);
        }, this);
        this.select();
      } else {
        this.$el.addClass('js-rec-no-results');
      }
      return this;
    },

    _renderLabel: function (label) {
      var el = this.labelTemplate(label);
      return (el instanceof $ ? el : $(el)).addClass('js-rec-label');
    },

    _renderResult: function (result) {
      var el = this.resultTemplate(result);
      return (el instanceof $ ? el : $(el)).addClass('js-rec-result');
    }
  };

  // Extend `Rec.prototype`.
  for (var name in proto) Rec.prototype[name] = proto[name];
})();
<commit_msg>Fix #1, Hide results and blur on ESC<commit_after>(function () {
  'use strict';

  // Store a local reference to jQuery and Underscorea.
  var $ = window.jQuery;
  var _ = window._;

  var keyDirMap = {'38': 'prev', '40': 'next'};
  var ctrlKeyDirMap = {'80': 'prev', '78': 'next'};

  // Delay a callback `n` ms or invoke immediately if 0.
  var delay = function (cb, n) { return !n ? cb() : _.delay(cb, n); };

  // Direct event bindings to `$el`.
  var directEvents = {
    mouseover: function () { this._hover = true; },
    mouseout: function () {
      this._hover = false;
      _.defer(_.bind(function () {
        if (!this._focus && !this._hover) this.hide();
      }, this));
    }
  };

  // Delegated event bindings to children of `$el`.
  var delegatedEvents = {

    // Delegated events for the input area.
    '.js-rec-input': {

      // Update the `_focus` flag and show the latest query results.
      focus: function (ev) {
        this._focus = true;
        this.query($(ev.currentTarget).val()).show();
      },

      // Check for the up/down keys and enter key, and finally submit a new
      // query.
      keydown: function (ev) {
        var key = ev.which;
        var dir = keyDirMap[key] || (ev.ctrlKey && ctrlKeyDirMap[key]);
        if (dir) {
          this[dir]();
          return ev.preventDefault();
        }
        if (key === 13) {
          var $selected = this.$el.find('.js-rec-selected');
          if ($selected.length) {
            $selected[0].click();
            return ev.preventDefault();
          }
        }
        var $input = $(ev.currentTarget);
        if (key === 27) {
          $input.blur();
          this.hide();
          return ev.preventDefault();
        }
        _.defer(_.bind(function () { this.query($input.val()); }, this));
      },

      // Hide the results when focus is lost.
      blur: function () {
        this._focus = false;
        if (!this._hover) this.hide();
      }
    },

    // Delegated events for invidiual results.
    '.js-rec-result': {

      // Select the result that is being hovered over.
      mouseover: function (ev) { this.select($(ev.currentTarget)); },

      // Optionally clear and hide the input and results on result click.
      click: function () {
        var $input = this.$el.find('.js-rec-input');
        if (this.clearOnClick) $input.val('').focus();
        if (this.hideOnClick) {
          $input.blur();
          this.hide();
        }
      }
    }
  };

  // Create the `Rec` constructor.
  //
  // ```js
  // var rec = new Rec('#my-rec-container', {
  //   url: '/search',
  //   template: jst['search-result']
  // });
  // ```
  var Rec = window.Rec = function (el, options) {

    // Define direct and delegated event hashes unique to this instance. This
    // will allow a clean unbinding of these events without worry about any
    // event collisions.
    this._directEvents = {};
    _.each(directEvents, function (cb, key) {
      this._directEvents[key] = _.bind(cb, this);
    }, this);
    this._delegatedEvents = {};
    _.each(delegatedEvents, function (evs, selector) {
      var bound = this._delegatedEvents[selector] = {};
      _.each(evs, function (cb, name) {
        bound[name] = _.bind(cb, this);
      }, this);
    }, this);

    // Create a cache object to store results.
    this._cached = {};

    // Track concurrent fetches for showing/hiding the loading spinner.
    this._fetchQueue = 0;

    // Set the container element.
    this.setElement(el);

    // Extend the instance with its options.
    for (var name in options) this[name] = options[name];
  };

  // Define `prototype` properties and methods for `Olay`.
  var proto = {

    // How long since the last keystroke should rec wait to call `fetch`?
    delay: 250,

    // How many results should be displayed? `0` means as many as are returned
    // by `fetch`.
    limit: 0,

    // Should results be cached? Any cross-session caching will need to be
    // handled by your `fetch` method, but in-memory caching is free with rec.
    cache: true,

    // Should the first result automatically be selected? In other words, will
    // hitting return fire the first recommended result or the broad result?
    selectFirst: true,

    // Should the results be hidden and focus blurred when a result is clicked?
    hideOnClick: false,

    // Should the input be cleared when a result is clicked?
    clearOnClick: true,

    // The query string key that will be used with the default `fetch` method.
    queryKey: 'q',

    // Override this template functions to return your labels and results the
    // way you want them. If you are not using the `groupBy` option,
    // `labelTemplate` will be ignored.
    labelTemplate: function (label) { return '
' + label + '
'; },
    resultTemplate: function (result) { return '
' + result + '
'; },

    // Parse the query before sending it to `fetch`. This allows you to clean up
    // bad characters, extra spaces, etc...
    parse: function (q) {
      return q.replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, '').toLowerCase();
    },

    // `filter` works alongside `cache` to intelligently predict possible
    // results while waiting for a server request to come back. Override this
    // function with logic that best matches your server's filter logic.
    filter: function (q, result) {
      var str = (result.title || '').toLowerCase();
      return _.every(q.split(' '), function (word) {
        return ~str.indexOf(word);
      });
    },

    // The default `fetch` is designed for simple, JSON AJAX requests. Override
    // thid function to suite your application's needs.
    fetch: function (q, cb) {
      var options = this.fetchOptions || {};
      (options.data || (options.data = {}))[this.queryKey] = q;
      $.ajax(_.extend({
        type: 'get',
        dataType: 'json',
        success: function (results) { cb(null, results); },
        error: function (xhr) { cb(xhr.responseText); }
      }, options));
      return this;
    },

    query: function (q) {

      // Parse the raw query string.
      q = this.parse(q);

      // Compare to the last query and store so we don't waste any time.
      if (q === this._lastQ) return this;
      this._lastQ = q;

      // Clear the old `fetch` request if one was about to fire.
      if (this._timeout) {
        clearTimeout(this._timeout);
        this._timeout = null;
        if (!--this._fetchQueue) this.$el.removeClass('js-rec-loading');
      }

      // Looked for a cache result before trying to load.
      if (!this._getCached(q)) {

        // Add the loading class.
        ++this._fetchQueue;
        this.$el.addClass('js-rec-loading');

        // Start a timeout to `fetch`
        var self = this;
        this._timeout = delay(function () {
          self._timeout = null;
          self.fetch(q, function (er, results) {
            if (!--self._fetchQueue) self.$el.removeClass('js-rec-loading');
            if (!er) self._setCached(q, results);
            self._render();
          });
        }, this.delay);
      }
      return this._render();
    },

    // Set the input element the rec will watch.
    setElement: function (el) {
      if (this.$el) this._unbind();
      this.$el = el instanceof $ ? el : $(el);
      return this._bind();
    },

    show: function () {
      this.$el.find('.js-rec-results').removeAttr('style');
      this.select();
      return this;
    },

    hide: function () {
      this.$el.find('.js-rec-results').css('display', 'none');
      return this;
    },

    select: function ($el) {
      this.$el.find('.js-rec-selected').removeClass('js-rec-selected');
      if (!$el && this.selectFirst) {
        $el = this.$el.find('.js-rec-result').first();
      }
      if ($el) $el.addClass('js-rec-selected');
      return this;
    },

    prev: function () {
      return this.select(
        this.$el.find('.js-rec-selected').prevAll('.js-rec-result').first()
      );
    },

    next: function () {
      var $selected = this.$el.find('.js-rec-selected');
      var $next = $selected.nextAll('.js-rec-result').first();
      if (!$selected.length) {
        $next = this.$el.find('.js-rec-result').first();
      } else if (!$next.length) {
        return this;
      }
      return this.select($next);
    },

    _getCached: function (q) {
      return this.cache && this._cached[q];
    },

    _setCached: function (q, results) {
      results = results instanceof Array ? results : [];
      this._cached[q] = _.groupBy(results, this.groupBy || 'undefined');
      return this;
    },

    _getFiltered: function (q) {
      if (!this.filter) return null;
      var cached;
      for (var i = q.length - 1; i > 0; --i) {
        if (cached = this._getCached(q.slice(0, i))) break;
      }
      if (!cached) return null;
      var filter = _.bind(this.filter, this, q);
      var matches = _.reduce(cached, function (matches, results, label) {
        var filtered = _.filter(results, filter);
        if (filtered.length) matches[label] = filtered;
        return matches;
      }, {});
      return _.size(matches) ? matches : null;
    },

    // Bind (or unbind) delegated events from a multi-level object.
    _bind: function (unbind) {
      var $el = this.$el;
      var method = unbind ? 'off': 'on';
      $el[method](this._directEvents);
      _.each(this._delegatedEvents, function (evs, selector) {
        _.each(evs, function (cb, name) { $el[method](name, selector, cb); });
      });
      return this;
    },

    // Inverse `_bind`.
    _unbind: function () { return this._bind('off'); },

    // Build the elements for the most recent query.
    _render: function () {
      var q = this._lastQ;
      var results = this._getCached(q) || this._getFiltered(q);
      this.$el.removeClass('js-rec-no-results');
      this.$el.find('.js-rec-result, .js-rec-label').remove();
      var $results = this.$el.find('.js-rec-results');
      if (!results) return this;
      if (_.size(results)) {
        var limit = this.limit;
        var count = 0;
        _.each(results, function (results, label) {
          if (limit && count === limit) return;
          if (label !== 'undefined') $results.append(this._renderLabel(label));
          _.each(results, function (result) {
            if (limit && count === limit) return;
            $results.append(this._renderResult(result));
            ++count;
          }, this);
        }, this);
        this.select();
      } else {
        this.$el.addClass('js-rec-no-results');
      }
      return this;
    },

    _renderLabel: function (label) {
      var el = this.labelTemplate(label);
      return (el instanceof $ ? el : $(el)).addClass('js-rec-label');
    },

    _renderResult: function (result) {
      var el = this.resultTemplate(result);
      return (el instanceof $ ? el : $(el)).addClass('js-rec-result');
    }
  };

  // Extend `Rec.prototype`.
  for (var name in proto) Rec.prototype[name] = proto[name];
})();
<|endoftext|>


<commit_before>/**
 * React Static Boilerplate
 * https://github.com/kriasoft/react-static-boilerplate
 *
 * Copyright © 2015-present Kriasoft, LLC. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

/* eslint-disable no-console, global-require */

const fs = require('fs');
const del = require('del');
const ejs = require('ejs');
const webpack = require('webpack');
const firebase = require('firebase-tools');
const browserSync = require('browser-sync');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');

// TODO: Update configuration settings
const config = {
  title: 'Link-SF',           // Your website title
  url: 'https://link-sf.com', // Your website URL
  project: 'link-sf',         // Firebase project. See README.md -> How to Deploy
  trackingID: 'UA-XXXXX-Y',   // Google Analytics Site's ID
};

const tasks = new Map(); // The collection of automation tasks ('clean', 'build', 'publish', etc.)

function run(task) {
  const start = new Date();
  console.log(`Starting '${task}'...`);
  return Promise.resolve().then(() => tasks.get(task)()).then(() => {
    console.log(`Finished '${task}' after ${new Date().getTime() - start.getTime()}ms`);
  }, err => console.error(err.stack));
}

//
// Clean up the output directory
// -----------------------------------------------------------------------------
tasks.set('clean', () => del(['public/dist/*', '!public/dist/.git'], { dot: true }));

//
// Copy ./index.html into the /public folder
// -----------------------------------------------------------------------------
tasks.set('html', () => {
  const webpackConfig = require('./webpack.config');
  const assets = JSON.parse(fs.readFileSync('./public/dist/assets.json', 'utf8'));
  const template = fs.readFileSync('./public/index.ejs', 'utf8');
  const render = ejs.compile(template, { filename: './public/index.ejs' });
  const output = render({ debug: webpackConfig.debug, bundle: assets.main.js, config });
  fs.writeFileSync('./public/index.html', output, 'utf8');
});

//
// Generate sitemap.xml
// -----------------------------------------------------------------------------
tasks.set('sitemap', () => {
  const urls = require('./routes.json')
    .filter(x => !x.path.includes(':'))
    .map(x => ({ loc: x.path }));
  const template = fs.readFileSync('./public/sitemap.ejs', 'utf8');
  const render = ejs.compile(template, { filename: './public/sitemap.ejs' });
  const output = render({ config, urls });
  fs.writeFileSync('public/sitemap.xml', output, 'utf8');
});

//
// Bundle JavaScript, CSS and image files with Webpack
// -----------------------------------------------------------------------------
tasks.set('bundle', () => {
  const webpackConfig = require('./webpack.config');
  return new Promise((resolve, reject) => {
    webpack(webpackConfig).run((err, stats) => {
      if (err) {
        reject(err);
      } else {
        console.log(stats.toString(webpackConfig.stats));
        resolve();
      }
    });
  });
});

//
// Build website into a distributable format
// -----------------------------------------------------------------------------
tasks.set('build', () => Promise.resolve()
  .then(() => run('clean'))
  .then(() => run('bundle'))
  .then(() => run('html'))
  .then(() => run('sitemap'))
);

//
// Build and publish the website
// -----------------------------------------------------------------------------
tasks.set('publish', () => {
  global.DEBUG = process.argv.includes('--debug') || false;
  return run('build')
    .then(() => firebase.login({ nonInteractive: false }))
    .then(() => firebase.deploy({
      project: config.project,
      cwd: __dirname,
    }))
    .then(() => { setTimeout(() => process.exit()); });
});

//
// Build website and launch it in a browser for testing (default)
// -----------------------------------------------------------------------------
tasks.set('start', () => {
  global.HMR = !process.argv.includes('--no-hmr'); // Hot Module Replacement (HMR)
  const template = fs.readFileSync('./public/index.ejs', 'utf8');
  const render = ejs.compile(template, { filename: './public/index.ejs' });
  const output = render({ debug: true, bundle: '/dist/main.js', config });
  fs.writeFileSync('./public/index.html', output, 'utf8');
  const webpackConfig = require('./webpack.config');
  const bundler = webpack(webpackConfig);
  return new Promise(resolve => {
    browserSync({
      server: {
        baseDir: 'public',

        middleware: [
          webpackDevMiddleware(bundler, {
            // IMPORTANT: dev middleware can't access config, so we should
            // provide publicPath by ourselves
            publicPath: webpackConfig.output.publicPath,

            // pretty colored output
            stats: webpackConfig.stats,

            // for other settings see
            // http://webpack.github.io/docs/webpack-dev-middleware.html
          }),

          // bundler should be the same as above
          webpackHotMiddleware(bundler),

          // Serve index.html for all unknown requests
          (req, res, next) => {
            if (req.headers.accept.startsWith('text/html')) {
              req.url = '/index.html'; // eslint-disable-line no-param-reassign
            }
            next();
          },
        ],
      },

      // no need to watch '*.js' here, webpack will take care of it for us,
      // including full page reloads if HMR won't work
      files: [
        'public/**/*.css',
        'public/**/*.html',
      ],
    });

    resolve();
  });
});

// Execute the specified task or default one. E.g.: node run build
run(process.argv[2] || 'start');
<commit_msg>update run.js<commit_after>/**
 * React Static Boilerplate
 * https://github.com/kriasoft/react-static-boilerplate
 *
 * Copyright © 2015-present Kriasoft, LLC. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

/* eslint-disable no-console, global-require */

const fs = require('fs');
const del = require('del');
const ejs = require('ejs');
const webpack = require('webpack');
const firebase = require('firebase-tools');
const browserSync = require('browser-sync');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');

// TODO: Update configuration settings
const config = {
  title: 'Copenhelp',           // Your website title
  url: 'https://copenhelp.dk', // Your website URL
  project: 'Copenhelp',         // Firebase project. See README.md -> How to Deploy
  trackingID: 'UA-72743727-1',   // Google Analytics Site's ID
};

const tasks = new Map(); // The collection of automation tasks ('clean', 'build', 'publish', etc.)

function run(task) {
  const start = new Date();
  console.log(`Starting '${task}'...`);
  return Promise.resolve().then(() => tasks.get(task)()).then(() => {
    console.log(`Finished '${task}' after ${new Date().getTime() - start.getTime()}ms`);
  }, err => console.error(err.stack));
}

//
// Clean up the output directory
// -----------------------------------------------------------------------------
tasks.set('clean', () => del(['public/dist/*', '!public/dist/.git'], { dot: true }));

//
// Copy ./index.html into the /public folder
// -----------------------------------------------------------------------------
tasks.set('html', () => {
  const webpackConfig = require('./webpack.config');
  const assets = JSON.parse(fs.readFileSync('./public/dist/assets.json', 'utf8'));
  const template = fs.readFileSync('./public/index.ejs', 'utf8');
  const render = ejs.compile(template, { filename: './public/index.ejs' });
  const output = render({ debug: webpackConfig.debug, bundle: assets.main.js, config });
  fs.writeFileSync('./public/index.html', output, 'utf8');
});

//
// Generate sitemap.xml
// -----------------------------------------------------------------------------
tasks.set('sitemap', () => {
  const urls = require('./routes.json')
    .filter(x => !x.path.includes(':'))
    .map(x => ({ loc: x.path }));
  const template = fs.readFileSync('./public/sitemap.ejs', 'utf8');
  const render = ejs.compile(template, { filename: './public/sitemap.ejs' });
  const output = render({ config, urls });
  fs.writeFileSync('public/sitemap.xml', output, 'utf8');
});

//
// Bundle JavaScript, CSS and image files with Webpack
// -----------------------------------------------------------------------------
tasks.set('bundle', () => {
  const webpackConfig = require('./webpack.config');
  return new Promise((resolve, reject) => {
    webpack(webpackConfig).run((err, stats) => {
      if (err) {
        reject(err);
      } else {
        console.log(stats.toString(webpackConfig.stats));
        resolve();
      }
    });
  });
});

//
// Build website into a distributable format
// -----------------------------------------------------------------------------
tasks.set('build', () => Promise.resolve()
  .then(() => run('clean'))
  .then(() => run('bundle'))
  .then(() => run('html'))
  .then(() => run('sitemap'))
);

//
// Build and publish the website
// -----------------------------------------------------------------------------
tasks.set('publish', () => {
  global.DEBUG = process.argv.includes('--debug') || false;
  return run('build')
    .then(() => firebase.login({ nonInteractive: false }))
    .then(() => firebase.deploy({
      project: config.project,
      cwd: __dirname,
    }))
    .then(() => { setTimeout(() => process.exit()); });
});

//
// Build website and launch it in a browser for testing (default)
// -----------------------------------------------------------------------------
tasks.set('start', () => {
  global.HMR = !process.argv.includes('--no-hmr'); // Hot Module Replacement (HMR)
  const template = fs.readFileSync('./public/index.ejs', 'utf8');
  const render = ejs.compile(template, { filename: './public/index.ejs' });
  const output = render({ debug: true, bundle: '/dist/main.js', config });
  fs.writeFileSync('./public/index.html', output, 'utf8');
  const webpackConfig = require('./webpack.config');
  const bundler = webpack(webpackConfig);
  return new Promise(resolve => {
    browserSync({
      server: {
        baseDir: 'public',

        middleware: [
          webpackDevMiddleware(bundler, {
            // IMPORTANT: dev middleware can't access config, so we should
            // provide publicPath by ourselves
            publicPath: webpackConfig.output.publicPath,

            // pretty colored output
            stats: webpackConfig.stats,

            // for other settings see
            // http://webpack.github.io/docs/webpack-dev-middleware.html
          }),

          // bundler should be the same as above
          webpackHotMiddleware(bundler),

          // Serve index.html for all unknown requests
          (req, res, next) => {
            if (req.headers.accept.startsWith('text/html')) {
              req.url = '/index.html'; // eslint-disable-line no-param-reassign
            }
            next();
          },
        ],
      },

      // no need to watch '*.js' here, webpack will take care of it for us,
      // including full page reloads if HMR won't work
      files: [
        'public/**/*.css',
        'public/**/*.html',
      ],
    });

    resolve();
  });
});

// Execute the specified task or default one. E.g.: node run build
run(process.argv[2] || 'start');
<|endoftext|>


<commit_before>var LazyWatcher = require('./lib/lazy-watcher')

module.exports = Set

function Set (defaultValues) {
  var instance = new ProtoSet(defaultValues)
  var observable = instance.MutantSet.bind(instance)
  observable.add = instance.add.bind(instance)
  observable.clear = instance.clear.bind(instance)
  observable.delete = instance.delete.bind(instance)
  observable.has = instance.has.bind(instance)
  observable.set = instance.set.bind(instance)
  observable.get = instance.get.bind(instance)
  observable.getLength = instance.getLength.bind(instance)
  return observable
}

// optimise memory usage
function ProtoSet (defaultValues) {
  var self = this
  self.object = []
  self.sources = []
  self.releases = []
  self.binder = LazyWatcher.call(self, self._update, self._listen, self._unlisten)
  self.binder.value = this.object

  if (defaultValues && defaultValues.length) {
    defaultValues.forEach(function (valueOrObs) {
      if (!~self.sources.indexOf(valueOrObs)) {
        self.sources.push(valueOrObs)
      }
    })
    this.update()
  }
}

ProtoSet.prototype.MutantSet = function (listener) {
  if (!listener) {
    return this.binder.getValue()
  }
  return this.binder.addListener(listener)
}

ProtoSet.prototype.add = function (valueOrObs) {
  if (!~this.sources.indexOf(valueOrObs)) {
    this.sources.push(valueOrObs)
    if (this.binder.live) {
      this.releases[this.sources.length - 1] = this._bind(valueOrObs)
    }
    this.binder.onUpdate()
  }
}

ProtoSet.prototype.clear = function () {
  this.releases.forEach(tryInvoke)
  this.sources.length = 0
  this.releases.length = 0
  this.binder.onUpdate()
}

ProtoSet.prototype.delete = function (valueOrObs) {
  var index = this.sources.indexOf(valueOrObs)
  if (~index) {
    this.sources.splice(index, 1)
    this.releases.splice(index, 1).forEach(tryInvoke)
    this.binder.onUpdate()
  }
}

ProtoSet.prototype.has = function (valueOrObs) {
  return !!~this.object.indexOf(valueOrObs)
}

ProtoSet.prototype.set = function (values) {
  var self = this
  self.sources.length = 0
  values.forEach(function (value) {
    self.sources.push(value)
  })
  self.binder.onUpdate()
}

ProtoSet.prototype.get = function (index) {
  return this.sources[index]
}

ProtoSet.prototype.getLength = function () {
  return this.sources.length
}

ProtoSet.prototype._bind = function (valueOrObs) {
  return typeof valueOrObs === 'function' ? valueOrObs(this.binder.onUpdate) : null
}

ProtoSet.prototype._listen = function () {
  var self = this
  self.sources.forEach(function (obs, i) {
    self.releases[i] = self._bind(obs)
  })
}

ProtoSet.prototype._unlisten = function () {
  this.releases.forEach(tryInvoke)
  this.releases.length = 0
}

ProtoSet.prototype._update = function () {
  var currentValues = this.object.map(get)
  var newValues = this.sources.map(resolve)
  currentValues.filter(notIncluded, newValues).forEach(removeFrom, this.object)
  newValues.filter(notIncluded, currentValues).forEach(addTo, this.object)
  return true
}

function get (value) {
  return value
}

function resolve (source) {
  return typeof source === 'function' ? source() : source
}

function notIncluded (value) {
  return !~this.indexOf(value)
}

function removeFrom (item) {
  var index = this.indexOf(item)
  if (~index) {
    this.splice(index, 1)
  }
}

function addTo (item) {
  this.push(item)
}

function tryInvoke (func) {
  if (typeof func === 'function') {
    func()
  }
}
<commit_msg>set: fix setting empty value<commit_after>var LazyWatcher = require('./lib/lazy-watcher')

module.exports = Set

function Set (defaultValues) {
  var instance = new ProtoSet(defaultValues)
  var observable = instance.MutantSet.bind(instance)
  observable.add = instance.add.bind(instance)
  observable.clear = instance.clear.bind(instance)
  observable.delete = instance.delete.bind(instance)
  observable.has = instance.has.bind(instance)
  observable.set = instance.set.bind(instance)
  observable.get = instance.get.bind(instance)
  observable.getLength = instance.getLength.bind(instance)
  return observable
}

// optimise memory usage
function ProtoSet (defaultValues) {
  var self = this
  self.object = []
  self.sources = []
  self.releases = []
  self.binder = LazyWatcher.call(self, self._update, self._listen, self._unlisten)
  self.binder.value = this.object

  if (defaultValues && defaultValues.length) {
    defaultValues.forEach(function (valueOrObs) {
      if (!~self.sources.indexOf(valueOrObs)) {
        self.sources.push(valueOrObs)
      }
    })
    this.update()
  }
}

ProtoSet.prototype.MutantSet = function (listener) {
  if (!listener) {
    return this.binder.getValue()
  }
  return this.binder.addListener(listener)
}

ProtoSet.prototype.add = function (valueOrObs) {
  if (!~this.sources.indexOf(valueOrObs)) {
    this.sources.push(valueOrObs)
    if (this.binder.live) {
      this.releases[this.sources.length - 1] = this._bind(valueOrObs)
    }
    this.binder.onUpdate()
  }
}

ProtoSet.prototype.clear = function () {
  this.releases.forEach(tryInvoke)
  this.sources.length = 0
  this.releases.length = 0
  this.binder.onUpdate()
}

ProtoSet.prototype.delete = function (valueOrObs) {
  var index = this.sources.indexOf(valueOrObs)
  if (~index) {
    this.sources.splice(index, 1)
    this.releases.splice(index, 1).forEach(tryInvoke)
    this.binder.onUpdate()
  }
}

ProtoSet.prototype.has = function (valueOrObs) {
  return !!~this.object.indexOf(valueOrObs)
}

ProtoSet.prototype.set = function (values) {
  var self = this
  self.sources.length = 0
  if (Array.isArray(values)) {
    values.forEach(function (value) {
      self.sources.push(value)
    })
  }
  self.binder.onUpdate()
}

ProtoSet.prototype.get = function (index) {
  return this.sources[index]
}

ProtoSet.prototype.getLength = function () {
  return this.sources.length
}

ProtoSet.prototype._bind = function (valueOrObs) {
  return typeof valueOrObs === 'function' ? valueOrObs(this.binder.onUpdate) : null
}

ProtoSet.prototype._listen = function () {
  var self = this
  self.sources.forEach(function (obs, i) {
    self.releases[i] = self._bind(obs)
  })
}

ProtoSet.prototype._unlisten = function () {
  this.releases.forEach(tryInvoke)
  this.releases.length = 0
}

ProtoSet.prototype._update = function () {
  var currentValues = this.object.map(get)
  var newValues = this.sources.map(resolve)
  currentValues.filter(notIncluded, newValues).forEach(removeFrom, this.object)
  newValues.filter(notIncluded, currentValues).forEach(addTo, this.object)
  return true
}

function get (value) {
  return value
}

function resolve (source) {
  return typeof source === 'function' ? source() : source
}

function notIncluded (value) {
  return !~this.indexOf(value)
}

function removeFrom (item) {
  var index = this.indexOf(item)
  if (~index) {
    this.splice(index, 1)
  }
}

function addTo (item) {
  this.push(item)
}

function tryInvoke (func) {
  if (typeof func === 'function') {
    func()
  }
}
<|endoftext|>


<commit_before>Ext.define('TestCaseCopyApp', {
    extend: 'Rally.app.App',
    componentCls: 'app',
    items: [
        {
            xtype: 'rallytextfield',
            fieldLabel: 'Select the object to copy test cases from:'
        }
    ],

    launch: function() {
        var sourceButton = Ext.create('Rally.ui.Button', {
            text: 'Choose Object',
            listeners: {
                click: this._launchSourceArtifactChooser,
                scope: this
            }
        });

        var destUSButton = Ext.create('Rally.ui.Button', {
            text: 'Choose User Story',
            disabled: true,
            listeners: {
                click: this._launchDestUSChooser,
                scope: this
            }
        });

        var destTFButton = Ext.create('Rally.ui.Button', {
            text: 'Choose Test Folder',
            disabled: true,
            listeners: {
                click: this._launchDestTFChooser,
                scope: this
            }
        });

        var radioButton = Ext.create('Ext.form.RadioGroup', {
            xtype: 'radiogroup',
            fieldLabel: 'Do you want to associate new parent with the copy',
            items: [
                { boxLabel: 'Yes', name: 'rb', inputValue: '1' },
                { boxLabel: 'No', name: 'rb', inputValue: '0', checked: true}
            ],
            listeners: {
                change: function(component, newValue) {
                    this.parentingEnabled = newValue.rb === '1';

                    destUSButton.setDisabled(!this.parentingEnabled);
                    destTFButton.setDisabled(!this.parentingEnabled);
                },
                scope: this
            }
        });

        var copyButton = Ext.create('Rally.ui.Button', {
            text: 'Copy',
            disabled: true,
            listeners: {
                click: this._loadTestCasesForCopy,
                scope: this
            }
        });

        var destinationUSTextField = Ext.create('Rally.ui.TextField', {
            fieldLabel: 'User Story to Copy to:'
        });

        var destinationTFTextField = Ext.create('Rally.ui.TextField', {
            fieldLabel: 'Test Folder to Copy to:'
        });

        this.add(sourceButton);
        this.add(radioButton);
        this.add(destinationUSTextField);
        this.add(destUSButton);
        this.add(destinationTFTextField);
        this.add(destTFButton);
        this.add(copyButton);
    },

    _loadTestCasesForCopy: function() {
        this.selectedRecord.getCollection('TestCases').load().then({
            success: function(results) {
                // this.newTestCasesStore = Ext.create('Rally.data.wsapi.Store', {
                //     model: Ext.identityFn('TestCase'),
                //     autoLoad: false
                // });
                this.newTestCases = [];
                this._copyTestCases(results);
            },
            failure: function() {
                debugger;
            },
            scope: this
        });
    },

    _copyTestCases: function(results) {
        var sourceTestCase = results[0];

        sourceTestCase.getCollection('Attachments').load().then({
            success: function(attachments) {
                var record = Rally.data.util.Record.copyRecord(sourceTestCase);

                record.set('WorkProduct', null);
                record.set('TestFolder', null);
                record.set('c_SignoffApprovaloftestcase', null);
                record.set('c_SignoffReviewofinitialtestresult', null);
                record.set('c_TestCaseActual', null);
                record.set('c_TestCaseToDo', null);

                debugger;
                // record.set('Attachments', sourceTestCase.get('Attachments'));
                if (this.parentingEnabled) {
                    if (this.selectedUSRecord) {
                        record.set('WorkProduct', this.selectedUSRecord.data);
                    }

                    if (this.selectedTFRecord) {
                        record.set('TestFolder', this.selectedTFRecord.data);
                    }
                }

                record.save().then({
                    success: function(newTestCase) {
                        console.log('Test copied: ', newTestCase.get('FormattedID'));
                        // this.newTestCasesStore.add(newTestCase);
                        this.newTestCases.push(newTestCase);

                        results.splice(0, 1);

                        if (results.length) {
                            this._copyTestCases(results);
                        } else {
                            this._displayNewTestCases();
                        }
                    },
                    failure: function() {
                        debugger;
                    },
                    scope: this
                });
            },
            failure: function() {
                debugger;
            },
            scope: this
        });

    },

    _displayNewTestCases: function() {
        // this.newTestCases

        var grid = Ext.create('Rally.ui.grid.Grid', {
            columnCfgs: [
                'FormattedID',
                'Name'
            ],
            storeConfig: {
                model: 'TestCase',
                autoLoad: false
            }
        });

        _.each(this.newTestCases, function(testCase) {
            grid.store.add(testCase);
        });

        this.add(grid);
    },

    _launchSourceArtifactChooser: function() {
        Ext.create('Rally.ui.dialog.ChooserDialog', {
            artifactTypes: ['userstory', 'testfolder'],
            autoShow: true,
            title: 'Choose User Story or Test Folder',
            listeners: {
                artifactChosen: function(selectedRecord) {
                    this.selectedRecord = selectedRecord;

                    var textfield = this.items.getAt(0);
                    textfield.setValue(selectedRecord.get('FormattedID'));

                    var copyButton = this.items.getAt(7);
                    copyButton.enable();
                },
                scope: this
            },
            storeConfig: {
                fetch: ['TestCases']
            }
        });
    },

    _launchDestUSChooser: function() {
        Ext.create('Rally.ui.dialog.ChooserDialog', {
            artifactTypes: ['userstory'],
            autoShow: true,
            title: 'Choose User Story',
            listeners: {
                artifactChosen: function(selectedRecord) {
                    this.selectedUSRecord = selectedRecord;

                    var textfield = this.items.getAt(3);
                    textfield.setValue(selectedRecord.get('FormattedID'));
                },
                scope: this
            }
        });
    },

    _launchDestTFChooser: function() {
        Ext.create('Rally.ui.dialog.ChooserDialog', {
            artifactTypes: ['testfolder'],
            autoShow: true,
            title: 'Choose Test Folder',
            listeners: {
                artifactChosen: function(selectedRecord) {
                    this.selectedTFRecord = selectedRecord;

                    var textfield = this.items.getAt(5);
                    textfield.setValue(selectedRecord.get('FormattedID'));
                },
                scope: this
            }
        });
    }
});
<commit_msg>reset fields after copy action.<commit_after>Ext.define('TestCaseCopyApp', {
    extend: 'Rally.app.App',
    componentCls: 'app',
    items: [
        {
            xtype: 'rallytextfield',
            fieldLabel: 'Select the object to copy test cases from:'
        }
    ],

    launch: function() {
        var sourceButton = Ext.create('Rally.ui.Button', {
            text: 'Choose Object',
            listeners: {
                click: this._launchSourceArtifactChooser,
                scope: this
            }
        });

        var destUSButton = Ext.create('Rally.ui.Button', {
            text: 'Choose User Story',
            listeners: {
                click: this._launchDestUSChooser,
                scope: this
            }
        });

        var destTFButton = Ext.create('Rally.ui.Button', {
            text: 'Choose Test Folder',
            listeners: {
                click: this._launchDestTFChooser,
                scope: this
            }
        });

        var radioButton = Ext.create('Ext.form.RadioGroup', {
            xtype: 'radiogroup',
            fieldLabel: 'Do you want to associate new parent with the copy',
            items: [
                { boxLabel: 'Yes', name: 'rb', inputValue: '1', checked: true },
                { boxLabel: 'No', name: 'rb', inputValue: '0'}
            ],
            listeners: {
                change: function(component, newValue) {
                    this.parentingEnabled = newValue.rb === '1';

                    destUSButton.setDisabled(!this.parentingEnabled);
                    destTFButton.setDisabled(!this.parentingEnabled);
                },
                scope: this
            }
        });

        var copyButton = Ext.create('Rally.ui.Button', {
            text: 'Copy',
            disabled: true,
            listeners: {
                click: this._loadTestCasesForCopy,
                scope: this
            }
        });

        var destinationUSTextField = Ext.create('Rally.ui.TextField', {
            fieldLabel: 'User Story to Copy to:'
        });

        var destinationTFTextField = Ext.create('Rally.ui.TextField', {
            fieldLabel: 'Test Folder to Copy to:'
        });

        this.add(sourceButton);
        this.add(radioButton);
        this.add(destinationUSTextField);
        this.add(destUSButton);
        this.add(destinationTFTextField);
        this.add(destTFButton);
        this.add(copyButton);
    },

    _loadTestCasesForCopy: function() {
        this.selectedRecord.getCollection('TestCases').load().then({
            success: function(results) {
                this.newTestCases = [];
                this._copyTestCases(results);
            },
            failure: function() {
                debugger;
            },
            scope: this
        });
    },

    _copyTestCases: function(results) {
        var sourceTestCase = results[0];

        sourceTestCase.getCollection('Attachments').load().then({
            success: function(attachments) {
                var record = Rally.data.util.Record.copyRecord(sourceTestCase);

                record.set('WorkProduct', null);
                record.set('TestFolder', null);
                record.set('c_SignoffApprovaloftestcase', null);
                record.set('c_SignoffReviewofinitialtestresult', null);
                record.set('c_TestCaseActual', null);
                record.set('c_TestCaseToDo', null);

                if (this.parentingEnabled) {
                    if (this.selectedUSRecord) {
                        record.set('WorkProduct', this.selectedUSRecord.data);
                    }

                    if (this.selectedTFRecord) {
                        record.set('TestFolder', this.selectedTFRecord.data);
                    }
                }

                record.save().then({
                    success: function(newTestCase) {
                        console.log('Test copied: ', newTestCase.get('FormattedID'));
                        this.newTestCases.push(newTestCase);

                        results.splice(0, 1);

                        if (results.length) {
                            this._copyTestCases(results);
                        } else {
                            this._displayNewTestCases();
                            this._resetApp();
                        }
                    },
                    failure: function() {
                        debugger;
                    },
                    scope: this
                });
            },
            failure: function() {
                debugger;
            },
            scope: this
        });

    },

    _displayNewTestCases: function() {
        var grid = Ext.create('Rally.ui.grid.Grid', {
            columnCfgs: [
                'FormattedID',
                'Name'
            ],
            storeConfig: {
                model: 'TestCase',
                autoLoad: false
            }
        });

        _.each(this.newTestCases, function(testCase) {
            grid.store.add(testCase);
        });

        this.add(grid);
    },

    _resetApp: function() {
        this.items.getAt(0).setValue('');
        this.items.getAt(2).setValue({ rb: '1' });
        this.items.getAt(3).setValue('');
        this.items.getAt(5).setValue('');
        this.items.getAt(7).disable();

        delete this.selectedRecord;
        delete this.selectedUSRecord;
        delete this.selectedTFRecord;
    },

    _launchSourceArtifactChooser: function() {
        Ext.create('Rally.ui.dialog.ChooserDialog', {
            artifactTypes: ['userstory', 'testfolder'],
            autoShow: true,
            title: 'Choose User Story or Test Folder',
            listeners: {
                artifactChosen: function(selectedRecord) {
                    this.selectedRecord = selectedRecord;

                    var textfield = this.items.getAt(0);
                    textfield.setValue(selectedRecord.get('FormattedID'));

                    var copyButton = this.items.getAt(7);
                    copyButton.enable();
                },
                scope: this
            },
            storeConfig: {
                fetch: ['TestCases']
            }
        });
    },

    _launchDestUSChooser: function() {
        Ext.create('Rally.ui.dialog.ChooserDialog', {
            artifactTypes: ['userstory'],
            autoShow: true,
            title: 'Choose User Story',
            listeners: {
                artifactChosen: function(selectedRecord) {
                    this.selectedUSRecord = selectedRecord;

                    var textfield = this.items.getAt(3);
                    textfield.setValue(selectedRecord.get('FormattedID'));
                },
                scope: this
            }
        });
    },

    _launchDestTFChooser: function() {
        Ext.create('Rally.ui.dialog.ChooserDialog', {
            artifactTypes: ['testfolder'],
            autoShow: true,
            title: 'Choose Test Folder',
            listeners: {
                artifactChosen: function(selectedRecord) {
                    this.selectedTFRecord = selectedRecord;

                    var textfield = this.items.getAt(5);
                    textfield.setValue(selectedRecord.get('FormattedID'));
                },
                scope: this
            }
        });
    }
});
<|endoftext|>


<commit_before>Ext.define('Rally.ui.picker.SonOfMultiObjectPicker', {
    extend: 'Rally.ui.picker.MultiObjectPicker',
    alias: 'widget.sonofrallymultiobjectpicker',
    _createStoreAndExpand: function() {
        var storeCreator = Ext.create('Rally.data.DataStoreCreator', {
            modelType: this.modelType,
            storeConfig: this.storeConfig,
            storeType: this.storeType
        });
        this.mon(storeCreator, 'storecreate', function(store) {
            this.store = store;
            this.expand();
        }, this, {single: true});
        storeCreator.createStore();
    }
});

Ext.define('CustomApp', {
    extend: 'Rally.app.App',
    componentCls: 'app',

    dayRange: 30,
    severityValues: [],     // custom values for defect 'severity'
    priorityValues: [],     // custom values for defect 'priority'
    stateValues: [],        // custom values for defect 'state'

    launch: function() {
        Rally.sdk.dependencies.Analytics.load(this._loadDefectFilterValues, this);
    },

    // kickoff series of async value fetches for Defect attributes (severity, priority, etc)
    _loadDefectFilterValues: function() {
        Rally.data.ModelFactory.getModel({
            type: 'Defect',
            success: function(model) {
                this._loadDefectSeverityValues(model);
            },
            scope: this
        });
    },

    // Load custom defect 'severity' values for the filter pulldown
    _loadDefectSeverityValues: function(model) {
        this.severity = model.getField('Severity');
        var store = this.severity.getAllowedValueStore();
        store.load({
            callback: function(data) {
                _.each(data, function(el) {
                    console.log(el.data.StringValue);
                    if (el.data.StringValue !== "") {this.severityValues.push(el.data.StringValue);}
                    else {this.severityValues.push("None");}
                }, this);
                this._loadDayRangeSelector();
            },
            scope: this
        }, this);
    },

    // create the 30/60/90 day range selector
    _loadDayRangeSelector: function() {

        this.add({
            xtype: 'dayrangepicker',
            itemId: 'dayRangePicker',
            defaultSelection: '30',   // 30|60|90
            autoLoadSelection: true
        });

        this.down('#dayRangePicker').on({
            on30clicked: function() {
                this.dayRange = DayRangePicker.THIRTY;
                this._getChartData();
            },
            on60clicked: function() {
                this.dayRange = DayRangePicker.SIXTY;
                this._getChartData();
            },
            on90clicked: function() {
                this.dayRange = DayRangePicker.NINETY;
                this._getChartData();
            },
            scope: this
        });

        //this._multiSelect();
        this._getChartData();
    },

    _getChartFilters: function() {
        var daysAgo = Ext.Date.add(new Date(), Ext.Date.DAY, -this.dayRange);
        var startOnISOString = Rally.util.DateTime.toIsoString(daysAgo, true);

        var filters =
            [
                {
                    property: '_TypeHierarchy',
                    operator: 'in',
                    value: ['Defect']
                },
                {
                    property: 'Severity',
                    operator: 'in',
                    value: this.severityValues        // TODO: handle 'empty' or not selected severity
                },
                {
                    property: "Project",
                    value: this.getContext().getProject().ObjectID
                },
                {
                    property: "_ValidTo",
                    operator: ">=",
                    value: startOnISOString
                }
            ];

        return filters;

    },

    _getChartData: function() {

        var myFilters = this._getChartFilters();

        Ext.create('Rally.data.lookback.SnapshotStore', {
            listeners: {
                load: function(store,data,success) {
                  this._onDefectsLoaded(data);
                },
                scope: this
            },
            fetch: ['Name', 'Severity'],
            autoLoad: true,
            context: {
                workspace: this.getContext().getWorkspace()._ref,
                project: this.getContext().getProject()._ref,
                projectScopeUp: false,
                projectScopeDown: true
            },
            hydrate: ['Severity'],
            filters: myFilters,
            scope: this
        });

    },

    _getChartConfiguration: function() {
        return{
          chart: {
                type: 'area'
            },
            title: {
                text: 'Defect Trend'
            },
            subtitle: {
                text: 'Severity'
            },
            xAxis: {
                type: 'datetime'
            },
            yAxis: {
                title: {
                    text: 'Count'
                },
                labels: {
                    formatter: function() {
                        return this.value;
                    }
                }
            },
            tooltip: {
                shared: true
            },
            plotOptions: {
                area: {
                    stacking: 'normal',
                    lineColor: '#666666',
                    lineWidth: 1,
                    marker: {
                        lineWidth: 1,
                        lineColor: '#666666'
                    }
                }
            }
        };
    },

    _onDefectsLoaded: function (data) {

        var snapShotData = _.map(data,function(d){return d.data;});

        var metrics = [];
        _.each(this.severityValues, function(value) {
            metrics.push({
                as: value, f: 'filteredCount', filterField: 'Severity', filterValues: [value]
            });
        });

        // calculator config
        var config = {
          metrics: metrics,
          granularity: 'day',
          tz: 'America/Chicago',
          workDays: 'Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday'
        };

        var daysAgo = Ext.Date.add(new Date(), Ext.Date.DAY, -this.dayRange);

        var currentDate = Ext.Date.add(new Date(), Ext.Date.DAY, 0);

        var startOnISOString = Rally.util.DateTime.toIsoString(daysAgo, true);
        var upToDateISOString = Rally.util.DateTime.toIsoString(currentDate, true);

        calculator = new Rally.data.lookback.Lumenize.TimeSeriesCalculator(config);

        calculator.addSnapshots(snapShotData, startOnISOString, upToDateISOString);

        var hcConfig = [];
        _.each(this.severityValues, function(value) {
            hcConfig.push({name: value});
        });

        var hcData = Rally.data.lookback.Lumenize.arrayOfMaps_To_HighChartsSeries(calculator.getResults().seriesData, hcConfig);

        var dt;

        _.each(hcData, function(seriesObj) {
            dt = Ext.Date.format(daysAgo, 'Y-m-d').split("-");
            seriesObj.pointStart = Date.UTC(dt[0], dt[1] - 1, dt[2]);
            seriesObj.pointInterval = 24 * 3600 * 1000;
        });

        var myChart = Ext.create("Rally.ui.chart.Chart", {
            chartConfig: this._getChartConfiguration(),
            chartData: {
                series: hcData
            },
            itemId: 'myChart'
        });
        this.add(myChart);

        chart = this.down("#myChart");

        // HACK: need to remove load mask on high-charts due to lumenize lib loading issue
        var p = Ext.get(chart.id);
        elems = p.query("div.x-mask");
        _.each(elems, function(e) { e.remove(); });
        var elems = p.query("div.x-mask-msg");
        _.each(elems, function(e) { e.remove(); });
    }
});

<commit_msg>Added priority and state filtering<commit_after>Ext.define('Rally.ui.picker.SonOfMultiObjectPicker', {
    extend: 'Rally.ui.picker.MultiObjectPicker',
    alias: 'widget.sonofrallymultiobjectpicker',
    _createStoreAndExpand: function() {
        var storeCreator = Ext.create('Rally.data.DataStoreCreator', {
            modelType: this.modelType,
            storeConfig: this.storeConfig,
            storeType: this.storeType
        });
        this.mon(storeCreator, 'storecreate', function(store) {
            this.store = store;
            this.expand();
        }, this, {single: true});
        storeCreator.createStore();
    }
});

Ext.define('CustomApp', {
    extend: 'Rally.app.App',
    componentCls: 'app',

    dayRange: 30,
    severityValues: [],     // custom values for defect 'severity'
    priorityValues: [],     // custom values for defect 'priority'
    stateValues: [],        // custom values for defect 'state'

    launch: function() {
        Rally.sdk.dependencies.Analytics.load(this._loadDefectFilterValues, this);
    },

    // kickoff series of async value fetches for Defect attributes (severity, priority, etc)
    _loadDefectFilterValues: function() {
        Rally.data.ModelFactory.getModel({
            type: 'Defect',
            success: function(model) {
                this._loadDefectSeverityValues(model);
            },
            scope: this
        });
    },

    // Load custom defect 'severity' values for the filter pulldown
    _loadDefectSeverityValues: function(model) {
        this.severity = model.getField('Severity');
        var store = this.severity.getAllowedValueStore();
        store.load({
            callback: function(data) {
                _.each(data, function(el) {
                    if (el.data.StringValue !== "") {this.severityValues.push(el.data.StringValue);}
                    else {this.severityValues.push("None");}
                }, this);
                this._loadDefectPriorityValues(model);
            },
            scope: this
        }, this);
    },

    // Load custom defect 'priority' values for the filter pulldown
    _loadDefectPriorityValues: function(model) {
        var priority = model.getField('Priority');
        var store = priority.getAllowedValueStore();
        store.load({
            callback: function(data) {
                _.each(data, function(el) {
                    if (el.data.StringValue != "") {this.priorityValues.push(el.data.StringValue);}
                }, this);
                this._loadDefectStateValues(model);
            },
            scope: this
        }, this);
    },

    // Load custom defect 'state' values for the filter pulldown
    _loadDefectStateValues: function(model) {
        var state = model.getField('State');
        var store = state.getAllowedValueStore();
        store.load({
            callback: function(data) {
                _.each(data, function(el) {
                    if (el.data.StringValue != "") {this.stateValues.push(el.data.StringValue);}
                }, this);
                this._loadDayRangeSelector();
            },
            scope: this
        }, this);
    },

    // create the 30/60/90 day range selector
    _loadDayRangeSelector: function() {

        this.add({
            xtype: 'dayrangepicker',
            itemId: 'dayRangePicker',
            defaultSelection: '30',   // 30|60|90
            autoLoadSelection: true
        });

        this.down('#dayRangePicker').on({
            on30clicked: function() {
                this.dayRange = DayRangePicker.THIRTY;
                this._getChartData();
            },
            on60clicked: function() {
                this.dayRange = DayRangePicker.SIXTY;
                this._getChartData();
            },
            on90clicked: function() {
                this.dayRange = DayRangePicker.NINETY;
                this._getChartData();
            },
            scope: this
        });

        this._multiSelect();
        this._getChartData();
    },

    _multiSelect: function() {
        this._createMultiPicker(this.priorityValues, "Priorities");
        this._createMultiPicker(this.stateValues, "States");
        this.add(
        {
            xtype: 'button',
            text: 'Go!',
            handler: function() {
                this._getChartData();
            },
            scope: this
        });
    },

    _createMultiPicker: function(values, title)
    {
        var storeData = [];
        _.each(values, function(value, i) {
            storeData.push({Name: value, ObjectID: i, _CreatedAt: i});
        });

        this.add({
            xtype: 'sonofrallymultiobjectpicker',
            itemId: title,
            modelType: 'attributedefinition',
            config: {selectionKey: '_CreatedAt'},
            placeholderText: title,
            storeConfig: {
                data: storeData
            },
            listeners:
            {
                select: function() {
                    console.log(this._getSelectedValues("#" + title));
                },
                scope: this
            },
            storeType: 'Rally.data.custom.Store'
        });
    },

    _getSelectedValues: function(id) {
        var picker = this.down(id);
        var selected = [];
        if (picker) {
            _.each(picker.selectedValues.items, function(val) {
                selected.push(val.data.Name);
            });
        }
        return selected;
    },

    _getChartFilters: function() {
        var daysAgo = Ext.Date.add(new Date(), Ext.Date.DAY, -this.dayRange);
        var startOnISOString = Rally.util.DateTime.toIsoString(daysAgo, true);

        var filters =
            [
                {
                    property: '_TypeHierarchy',
                    operator: 'in',
                    value: ['Defect']
                },
                {
                    property: 'Severity',
                    operator: 'in',
                    value: this.severityValues        // TODO: handle 'empty' or not selected severity
                },
                {
                    property: "Project",
                    value: this.getContext().getProject().ObjectID
                },
                {
                    property: "_ValidTo",
                    operator: ">=",
                    value: startOnISOString
                }
            ];

            var stateValues = this._getSelectedValues("#States");
            if (stateValues.length)
            {
                filters.push({
                    property: "State",
                    operator: "in",
                    value: stateValues
                });
            }

            var priorityValues = this._getSelectedValues("#Priorities");
            if (priorityValues.length)
            {
                filters.push({
                    property: "Priority",
                    operator: "in",
                    value: priorityValues
                });
            }

        return filters;

    },

    _getChartData: function() {

        if (this.down('#myChart')) {
            this.down('#myChart').destroy();
        }

        var myFilters = this._getChartFilters();

        Ext.create('Rally.data.lookback.SnapshotStore', {
            listeners: {
                load: function(store,data,success) {
                  this._onDefectsLoaded(data);
                },
                scope: this
            },
            fetch: ['Name', 'Severity'],
            autoLoad: true,
            context: {
                workspace: this.getContext().getWorkspace()._ref,
                project: this.getContext().getProject()._ref,
                projectScopeUp: false,
                projectScopeDown: true
            },
            hydrate: ['Severity'],
            filters: myFilters,
            scope: this
        });

    },

    _getChartConfiguration: function() {
        return{
          chart: {
                type: 'area'
            },
            title: {
                text: 'Defect Trend'
            },
            subtitle: {
                text: 'Severity'
            },
            xAxis: {
                type: 'datetime'
            },
            yAxis: {
                title: {
                    text: 'Count'
                },
                labels: {
                    formatter: function() {
                        return this.value;
                    }
                }
            },
            tooltip: {
                shared: true
            },
            plotOptions: {
                area: {
                    stacking: 'normal',
                    lineColor: '#666666',
                    lineWidth: 1,
                    marker: {
                        lineWidth: 1,
                        lineColor: '#666666'
                    }
                }
            }
        };
    },

    _onDefectsLoaded: function (data) {

        var snapShotData = _.map(data,function(d){return d.data;});

        var metrics = [];
        _.each(this.severityValues, function(value) {
            metrics.push({
                as: value, f: 'filteredCount', filterField: 'Severity', filterValues: [value]
            });
        });

        // calculator config
        var config = {
          metrics: metrics,
          granularity: 'day',
          tz: 'America/Chicago',
          workDays: 'Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday'
        };

        var daysAgo = Ext.Date.add(new Date(), Ext.Date.DAY, -this.dayRange);

        var currentDate = Ext.Date.add(new Date(), Ext.Date.DAY, 0);

        var startOnISOString = Rally.util.DateTime.toIsoString(daysAgo, true);
        var upToDateISOString = Rally.util.DateTime.toIsoString(currentDate, true);

        calculator = new Rally.data.lookback.Lumenize.TimeSeriesCalculator(config);

        calculator.addSnapshots(snapShotData, startOnISOString, upToDateISOString);

        var hcConfig = [];
        _.each(this.severityValues, function(value) {
            hcConfig.push({name: value});
        });

        var hcData = Rally.data.lookback.Lumenize.arrayOfMaps_To_HighChartsSeries(calculator.getResults().seriesData, hcConfig);

        var dt;

        _.each(hcData, function(seriesObj) {
            dt = Ext.Date.format(daysAgo, 'Y-m-d').split("-");
            seriesObj.pointStart = Date.UTC(dt[0], dt[1] - 1, dt[2]);
            seriesObj.pointInterval = 24 * 3600 * 1000;
        });

        var myChart = Ext.create("Rally.ui.chart.Chart", {
            chartConfig: this._getChartConfiguration(),
            chartData: {
                series: hcData
            },
            itemId: 'myChart'
        });
        this.add(myChart);

        chart = this.down("#myChart");

        // HACK: need to remove load mask on high-charts due to lumenize lib loading issue
        var p = Ext.get(chart.id);
        elems = p.query("div.x-mask");
        _.each(elems, function(e) { e.remove(); });
        var elems = p.query("div.x-mask-msg");
        _.each(elems, function(e) { e.remove(); });
    }
});

<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Provider } from 'react-redux';
import styled from 'styled-components/native';
import { AppRegistry, AsyncStorage, LayoutAnimation } from 'react-native';
import { persistStore } from 'redux-persist';
import createEncryptor from 'redux-persist-transform-encrypt';
import DeviceInfo from 'react-native-device-info';
import md5 from 'md5';
import codePush from 'react-native-code-push';

import { colors } from 'config';
import { getCurrentLocale, configureLocale } from 'utils';
import { GitPoint } from './routes';
import { configureStore } from './root.store';

const Container = styled.View`
  align-items: center;
  background-color: ${() => colors.white}
  flex: 1;
  justify-content: center;
`;

const Logo = styled.Image`
  height: 100;
  width: 100;
`;

if (console) {
  console.disableYellowBox = true; // eslint-disable-line no-console
}

class App extends Component {
  static async initLocale() {
    const locale = await getCurrentLocale();

    configureLocale(locale);
  }

  constructor() {
    super();

    this.state = {
      rehydrated: false,
    };
  }

  componentWillMount() {
    const encryptor = createEncryptor({
      secretKey: md5(DeviceInfo.getUniqueID()),
    });

    persistStore(
      configureStore,
      {
        storage: AsyncStorage,
        transforms: [encryptor],
        blacklist: ['user'],
      },
      () => {
        this.setState({ rehydrated: true });
      }
    );

    this.constructor.initLocale();
  }

  componentDidMount() {
    if (!__DEV__) {
      codePush.sync({
        updateDialog: false,
        installMode: codePush.InstallMode.IMMEDIATE,
      });
    }
  }

  componentWillUpdate() {
    LayoutAnimation.spring();
  }

  render() {
    if (!this.state.rehydrated) {
      return (
        <Container>
          <Logo source={require('./src/assets/logo-black.png')} />
        </Container>
      );
    }

    return (
      <Provider store={configureStore}>
        <GitPoint onNavigationStateChange={null} />
      </Provider>
    );
  }
}

AppRegistry.registerComponent('GitPoint', () => App);
<commit_msg>Update App.js (#497)<commit_after>import React, { Component } from 'react';
import { Provider } from 'react-redux';
import styled from 'styled-components/native';
import { AppRegistry, AsyncStorage, LayoutAnimation } from 'react-native';
import { persistStore } from 'redux-persist';
import createEncryptor from 'redux-persist-transform-encrypt';
import DeviceInfo from 'react-native-device-info';
import md5 from 'md5';
import codePush from 'react-native-code-push';

import { colors } from 'config';
import { getCurrentLocale, configureLocale } from 'utils';
import { GitPoint } from './routes';
import { configureStore } from './root.store';

const Container = styled.View`
  align-items: center;
  background-color: ${colors.white}
  flex: 1;
  justify-content: center;
`;

const Logo = styled.Image`
  height: 100;
  width: 100;
`;

if (console) {
  console.disableYellowBox = true; // eslint-disable-line no-console
}

class App extends Component {
  static async initLocale() {
    const locale = await getCurrentLocale();

    configureLocale(locale);
  }

  constructor() {
    super();

    this.state = {
      rehydrated: false,
    };
  }

  componentWillMount() {
    const encryptor = createEncryptor({
      secretKey: md5(DeviceInfo.getUniqueID()),
    });

    persistStore(
      configureStore,
      {
        storage: AsyncStorage,
        transforms: [encryptor],
        blacklist: ['user'],
      },
      () => {
        this.setState({ rehydrated: true });
      }
    );

    this.constructor.initLocale();
  }

  componentDidMount() {
    if (!__DEV__) {
      codePush.sync({
        updateDialog: false,
        installMode: codePush.InstallMode.IMMEDIATE,
      });
    }
  }

  componentWillUpdate() {
    LayoutAnimation.spring();
  }

  render() {
    if (!this.state.rehydrated) {
      return (
        <Container>
          <Logo source={require('./src/assets/logo-black.png')} />
        </Container>
      );
    }

    return (
      <Provider store={configureStore}>
        <GitPoint onNavigationStateChange={null} />
      </Provider>
    );
  }
}

AppRegistry.registerComponent('GitPoint', () => App);
<|endoftext|>


<commit_before>// This App is supposed to find Test Sets and then list the Test Cases within them
//
//
//A new comment is added
//
// Verify connector function
// Prepare to add pull requests
//
// This is a change for the better
//
//
//This a Test of vcs connector 2.0.5
//
//Creating a change in the code
//
// Test the logistics
// A change for the better
// Added by JC
//

// A change that is needed for my PR
//
//
// Times they are a changing
//
=======

Ext.define('CustomApp', {
    extend: 'Rally.app.App',
    componentCls: 'app',
    items:{ html:'App SDK 2.0rc3 Docs'},
    launch: function() {
    		var deStore;
// Adding a comment to this file - JC
//
    		var panel1 = Ext.create( 'Ext.panel.Panel', {
    			layout : {
    				type : 'hbox', // Arrange child items vertically
    				align : 'middle', // Each takes up full width
    			},
    			width : '100%',
    			bodyPadding : 10,
    			bodyStyle : {
    				background : '#9A9494',
    			},
    			items : [
    					{
    						xtype : 'container',
    						itemId : 'iterationFilter'
    					}, {
    						xtype : 'rallybutton',
    						text : 'Print Summary',
    						handler : function () {
    							this._printSummary();
    						},
    						style : {
    							marginRight : '10px',
    							marginLeft : '10px'
    						},
    						// flex:'1',
    						scope : this
    					}
    			/*
    			 * { xtype:'rallybutton', text: 'Print Defects', handler: function() { this._printDefects(); }, style: { marginLeft: '10px' },
    			 *
    			 * flex:'1', scope:this }
    			 */
    			]
    		} );
    		this.add( panel1 );
    		var panel2 = Ext.create( 'Ext.panel.Panel', {
    			layout : 'vbox',
    			width : '100%',
    			bodyPadding : 10,
    			bodyStyle : {
    				background : '#9A9494',
    			},
    			items : [

    				{
    					xtype : 'container',
    					layout : 'accordion',
    					itemId : 'testStatus',
    					width : '100%',
    					height : 500
    				}
    			]
    		} );

        // Some explanatory text for a change
        //
    		this.add( panel2 );
    		this._testSummary = [];
    		this._testResultList = [];
    		this._testSetTestList = [];
    		var TestSetStore;
    		var TestResultStore;
    		scope: this;

    		this.down( '#iterationFilter' ).add( {
    			xtype : 'rallyiterationcombobox',
    			itemId : 'iterationComboBox',
    			listeners : {
    				change : this._queryForTestSets,
    				ready : this._queryForTestSets,
    				scope : this
    			}
    		} );
    		;
    	},
    	_queryForTestSets : function () {

    		// if loading new iteration, destroy existing grids
    		if ( this._summaryGrid !== undefined ) {
    			this._summaryGrid.destroy();
    			this._Grid.destroy();
    			this._Grid2.destroy();
    		}

    		TestSetStore = Ext.create( 'Rally.data.WsapiDataStore', {
    			model : 'TestSet',
    			autoLoad : true,
    			storeId : 'TestSetStorer',
    			context : {
    				projectScopeUp : false,
    				projectScopeDown : true
    			},
    			filters : [
    					{
    						property : 'Project',
    						operator : '=',
    						value : this.getContext().getDataContext().project
    					}, {
    						property : 'Iteration.Name',
    						operator : '=',
    						value : this.down( '#iterationComboBox' ).getRecord().data.Name
    					}
    			],
    			fetch : [
    					'FormattedID', 'Name', 'TestCases', 'ObjectID', 'Project'
    			],
    			limit : 10000,
    			listeners : {
    				load : function ( store, data, success ) {
    					this._queryForTestResults( store, data )
    				},
    				scope : this
    			}
    		} );
    	},
});
<commit_msg>Updates US98<commit_after>// This App is supposed to find Test Sets and then list the Test Cases within them
//
//
//A new comment is added
//
// Verify connector function
// Prepare to add pull requests
//
// This is a change for the better
//
//
//This a Test of vcs connector 2.0.5
//
//Creating a change in the code
//
// Test the logistics
// A change for the better
// Added by JC
//

// A change that is needed for my PR
//
//
// Times they are a changing
//
=======

Ext.define('CustomApp', {
    extend: 'Rally.app.App',
    componentCls: 'app',
    items:{ html:'App SDK 2.0rc3 Docs'},
    launch: function() {
    		var deStore;
// Adding a comment to this file - JC
//
// Changes, Changes, more changes
//
    		var panel1 = Ext.create( 'Ext.panel.Panel', {
    			layout : {
    				type : 'hbox', // Arrange child items vertically
    				align : 'middle', // Each takes up full width
    			},
    			width : '100%',
    			bodyPadding : 10,
    			bodyStyle : {
    				background : '#9A9494',
    			},
    			items : [
    					{
    						xtype : 'container',
    						itemId : 'iterationFilter'
    					}, {
    						xtype : 'rallybutton',
    						text : 'Print Summary',
    						handler : function () {
    							this._printSummary();
    						},
    						style : {
    							marginRight : '10px',
    							marginLeft : '10px'
    						},
    						// flex:'1',
    						scope : this
    					}
    			/*
    			 * { xtype:'rallybutton', text: 'Print Defects', handler: function() { this._printDefects(); }, style: { marginLeft: '10px' },
    			 *
    			 * flex:'1', scope:this }
    			 */
    			]
    		} );
    		this.add( panel1 );
    		var panel2 = Ext.create( 'Ext.panel.Panel', {
    			layout : 'vbox',
    			width : '100%',
    			bodyPadding : 10,
    			bodyStyle : {
    				background : '#9A9494',
    			},
    			items : [

    				{
    					xtype : 'container',
    					layout : 'accordion',
    					itemId : 'testStatus',
    					width : '100%',
    					height : 500
    				}
    			]
    		} );

        // Some explanatory text for a change
        //
    		this.add( panel2 );
    		this._testSummary = [];
    		this._testResultList = [];
    		this._testSetTestList = [];
    		var TestSetStore;
    		var TestResultStore;
    		scope: this;

    		this.down( '#iterationFilter' ).add( {
    			xtype : 'rallyiterationcombobox',
    			itemId : 'iterationComboBox',
    			listeners : {
    				change : this._queryForTestSets,
    				ready : this._queryForTestSets,
    				scope : this
    			}
    		} );
    		;
    	},
    	_queryForTestSets : function () {

    		// if loading new iteration, destroy existing grids
    		if ( this._summaryGrid !== undefined ) {
    			this._summaryGrid.destroy();
    			this._Grid.destroy();
    			this._Grid2.destroy();
    		}

    		TestSetStore = Ext.create( 'Rally.data.WsapiDataStore', {
    			model : 'TestSet',
    			autoLoad : true,
    			storeId : 'TestSetStorer',
    			context : {
    				projectScopeUp : false,
    				projectScopeDown : true
    			},
    			filters : [
    					{
    						property : 'Project',
    						operator : '=',
    						value : this.getContext().getDataContext().project
    					}, {
    						property : 'Iteration.Name',
    						operator : '=',
    						value : this.down( '#iterationComboBox' ).getRecord().data.Name
    					}
    			],
    			fetch : [
    					'FormattedID', 'Name', 'TestCases', 'ObjectID', 'Project'
    			],
    			limit : 10000,
    			listeners : {
    				load : function ( store, data, success ) {
    					this._queryForTestResults( store, data )
    				},
    				scope : this
    			}
    		} );
    	},
});
<|endoftext|>