Gitter migration: Point people to app.gitter.im (rollout pt. 1)
[gitter.git] / public / js / utils / burst-calculator.js
blob41f20c517e3b260857e5a4768a8584d0bd14a50a
1 'use strict';
3 var _ = require('lodash');
4 var Backbone = require('backbone');
6 module.exports = (function() {
7   // const - 5 minutes window to merge messages into previous burst
8   var BURST_WINDOW = 5 * 60 * 1000;
10   /**
11    * toCollection() converts an Array to a Backbone.Collection
12    * we do this in order to separate business logic from infrastructure workaround
13    *
14    * collection   Array or Backbone.Collection - Structure in which a check is performed
15    * returns      Backbone.Collection - the converted structure
16    */
17   function toCollection(collection) {
18     if (_.isArray(collection)) return new Backbone.Collection(collection);
19     return collection;
20   }
22   /**
23    * findBurstAbove() finds the first burstStart above the given index
24    *
25    * index Number - index used to find burst start
26    * @return Number - the index of burstStart found above
27    */
28   function findBurstAbove(index) {
29     var chat = null;
30     if (index === 0) return index;
31     while (index--) {
32       chat = this.at(index);
33       if (chat.get('burstStart')) break;
34     }
35     return index;
36   }
38   /**
39    * findBurstBelow() finds the first burstStart below the given index
40    *
41    * index Number - index used to find burst start
42    * @return Number - the index of burstStart found below
43    */
44   function findBurstBelow(index) {
45     if (index === this.length - 1) return index;
46     var chat = null;
47     while (index < this.length) {
48       chat = this.at(index);
49       if (chat.get('burstStart')) break;
50       index++;
51     }
52     return index;
53   }
55   /**
56    * findSlice() triggers a parse based on a model, however it finds the correct `slice`
57    * of the chat-collection to be recalculated. To summarize: This function is responsible for calling parse
58    * with a subset of the collection.
59    *
60    * model    Backbone.Model - the model that is to be added to the collection
61    * returns  void - it simply calls parse(), which mutates the collection directly
62    */
63   function findSlice(model) {
64     if ('burstStart' in model.attributes) return; // already calculated bursts for this batch
65     var index = this.indexOf(model);
66     var start = findBurstAbove.call(this, index);
67     var end = findBurstBelow.call(this, index);
68     parse(this, start, end);
69   }
71   /**
72    * calculateBurst() analyses what criteria a chat-item meets and modifies the current burst state accordingly
73    *
74    * chat     Backbone.Model - the chat-item to be analysed
75    * state    Object - the current burst state
76    *
77    * returns  void - it mutates the object directly
78    */
79   function calculateBurst(chat, state) {
80     var fromUser = chat.get('fromUser');
81     var sent = chat.get('sent');
82     var user = fromUser && fromUser.username;
83     var time = (sent && new Date(sent).valueOf()) || Date.now();
85     if (chat.get('status')) {
86       state.burstStart = true;
87       state.status = true;
88       state.time = time;
89       return;
90     }
92     var outsideBurstWindow = time - state.time > BURST_WINDOW;
94     if (state.status || user !== state.user || outsideBurstWindow) {
95       state.burstStart = true;
96       state.user = user;
97       state.time = time;
99       state.status = false; // resetting the status, because it might have been true
100       return;
101     }
103     state.burstStart = false;
104     state.user = user;
105     return;
106   }
108   /**
109    * parse() detects each chat-item on a Collection regarding their burst status
110    *
111    * collection   Backbone.Collection - the collection to be iterated over
112    * start        Number - index set as the starting point of the iteration
113    * end          Number - index set as the ending point of the iteration
114    *
115    * returns Object - the mutated collection (Backbone.Collection.toJSON())
116    */
117   function parse(collection_, start, end) {
118     var collection = toCollection(collection_);
120     // pre-run checks
121     if (!collection) return;
122     start = typeof start !== 'undefined' ? start : 0; // start defaults at index 0
123     end = typeof end !== 'undefined' ? end : collection.length - 1; // end defaults at index n
125     var state = {
126       user: null,
127       burstStart: false,
128       status: false,
129       time: null,
130       parentId: null
131     };
133     collection.slice(start, end + 1).forEach(function(chat) {
134       if (chat.get('parentId')) return; // ignore thread messages for now
135       calculateBurst(chat, state);
136       chat.set('burstStart', state.burstStart);
137     });
139     return collection.toJSON();
140   }
142   /* public interface */
143   return {
144     calc: findSlice,
145     parse: parse
146   };
147 })();