diff --git a/bin/run.js b/bin/run.js new file mode 100755 index 0000000..99b1400 --- /dev/null +++ b/bin/run.js @@ -0,0 +1,24 @@ +#!/usr/bin/env node +var stat = require('node-static'), + path = require('path'), + http = require('http'), + pakg = require('../package.json'); + +var opts = { + 'serverInfo': 'burnchart/' + pakg.version +}; + +var dir = path.resolve(__dirname, '../public'); + +var file = new stat.Server(dir, opts); + +var server = http.createServer(function(req, res) { + req.addListener('end', function() { + file.serve(req, res); + }).resume(); +}).listen(process.argv[2]); + +server.on('listening', function() { + var addr = server.address(); + console.log('burnchart/' + pakg.version + ' started on http://' + addr.address + ':' + addr.port); +}); \ No newline at end of file diff --git a/docs/COVERAGE.html b/docs/COVERAGE.html index 39b4751..feed7d7 100644 --- a/docs/COVERAGE.html +++ b/docs/COVERAGE.html @@ -352,4 +352,4 @@ code .string { color: #5890AD } code .keyword { color: #8A6343 } code .number { color: #2F6FAD } -

Coverage

86%
483
419
64

/Users/radek/Dev/burnchart/src/models/config.coffee

100%
4
4
0
LineHitsSource
11(function() {
21 var Model;
3
41 Model = require('../utils/ractive/model.coffee');
5
61 module.exports = new Model({
7 'name': 'models/config',
8 "data": {
9 "firebase": "burnchart",
10 "provider": "github",
11 "fields": {
12 "milestone": ["closed_issues", "created_at", "description", "due_on", "number", "open_issues", "title", "updated_at"]
13 },
14 "chart": {
15 "off_days": [],
16 "size_label": /^size (\d+)$/,
17 "points": 'ONE_SIZE'
18 },
19 "request": {
20 "timeout": 5e3
21 }
22 }
23 });
24
25}).call(this);
26

/Users/radek/Dev/burnchart/src/models/projects.coffee

80%
122
98
24
LineHitsSource
11(function() {
21 var Model, config, lscache, semver, sortedIndex, stats, user, _,
3 __slice = [].slice;
4
51 _ = require('lodash');
6
71 lscache = require('lscache');
8
91 sortedIndex = require('sortedindex-compare');
10
111 semver = require('semver');
12
131 Model = require('../utils/ractive/model.coffee');
14
151 config = require('../models/config.coffee');
16
171 stats = require('../modules/stats.coffee');
18
191 user = require('./user.coffee');
20
211 module.exports = new Model({
22 'name': 'models/projects',
23 'data': {
24 'sortBy': 'priority',
25 'sortFns': ['progress', 'priority', 'name']
26 },
27 comparator: function() {
2814 var deIdx, defaults, list, sortBy, _ref;
2914 _ref = this.data, list = _ref.list, sortBy = _ref.sortBy;
3014 deIdx = (function(_this) {
3114 return function(fn) {
3214 return function() {
3311 var i, j, rest, _arg;
3411 _arg = arguments[0], rest = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
3511 i = _arg[0], j = _arg[1];
3611 return fn.apply(_this, [[list[i], list[i].milestones[j]]].concat(rest));
37 };
38 };
39 })(this);
4014 defaults = function(arr, hash) {
417 var i, item, k, keys, p, ref, v, _i, _len, _results;
427 _results = [];
437 for (_i = 0, _len = arr.length; _i < _len; _i++) {
4414 item = arr[_i];
4514 _results.push((function() {
4614 var _results1;
4714 _results1 = [];
4814 for (k in hash) {
4926 v = hash[k];
5026 ref = item;
5126 _results1.push((function() {
5226 var _j, _len1, _ref1, _results2;
5326 _ref1 = keys = k.split('.');
5426 _results2 = [];
5526 for (i = _j = 0, _len1 = _ref1.length; _j < _len1; i = ++_j) {
5666 p = _ref1[i];
5766 if (i === keys.length - 1) {
5826 _results2.push(ref[p] != null ? ref[p] : ref[p] = v);
59 } else {
6040 _results2.push(ref = ref[p] != null ? ref[p] : ref[p] = {});
61 }
62 }
6326 return _results2;
64 })());
65 }
6614 return _results1;
67 })());
68 }
697 return _results;
70 };
7114 switch (sortBy) {
72 case 'progress':
732 return deIdx(function(_arg, _arg1) {
741 var aM, aP, bM, bP;
751 aP = _arg[0], aM = _arg[1];
761 bP = _arg1[0], bM = _arg1[1];
771 defaults([aM, bM], {
78 'stats.progress.points': 0
79 });
801 return aM.stats.progress.points - bM.stats.progress.points;
81 });
82 case 'priority':
837 return deIdx(function(_arg, _arg1) {
846 var $a, $b, aM, aP, bM, bP, _ref1;
856 aP = _arg[0], aM = _arg[1];
866 bP = _arg1[0], bM = _arg1[1];
876 defaults([aM, bM], {
88 'stats.progress.time': 0,
89 'stats.days': 1e3
90 });
916 _ref1 = _.map([aM, bM], function(_arg2) {
9212 var stats;
9312 stats = _arg2.stats;
9412 return (stats.progress.points - stats.progress.time) * stats.days;
95 }), $a = _ref1[0], $b = _ref1[1];
966 return $b - $a;
97 });
98 case 'name':
995 return deIdx(function(_arg, _arg1) {
1004 var aM, aP, bM, bP, name, owner;
1014 aP = _arg[0], aM = _arg[1];
1024 bP = _arg1[0], bM = _arg1[1];
1034 if (owner = bP.owner.localeCompare(aP.owner)) {
1040 return owner;
105 }
1064 if (name = bP.name.localeCompare(aP.name)) {
1070 return name;
108 }
1094 if (semver.valid(bM.title) && semver.valid(aM.title)) {
1101 return semver.gt(bM.title, aM.title);
111 } else {
1123 return bM.title.localeCompare(aM.title);
113 }
114 });
115 default:
1160 return function() {
1170 return 0;
118 };
119 }
120 },
121 find: function(project) {
1220 return _.find(this.data.list, project);
123 },
124 exists: function() {
1250 return !!this.find.apply(this, arguments);
126 },
127 add: function(project) {
1280 if (!this.exists(project)) {
1290 return this.push('list', project);
130 }
131 },
132 findIndex: function(_arg) {
13314 var name, owner;
13414 owner = _arg.owner, name = _arg.name;
13514 return _.findIndex(this.data.list, {
136 owner: owner,
137 name: name
138 });
139 },
140 addMilestone: function(project, milestone) {
14114 var i, j;
14214 _.extend(milestone, {
143 'stats': stats(milestone)
144 });
14514 if ((i = this.findIndex(project)) < 0) {
1460 throw 500;
147 }
14814 if (project.milestones != null) {
1498 this.push("list." + i + ".milestones", milestone);
1508 j = this.data.list[i].milestones.length - 1;
151 } else {
1526 this.set("list." + i + ".milestones", [milestone]);
1536 j = 0;
154 }
15514 return this.sort([i, j], [project, milestone]);
156 },
157 saveError: function(project, err) {
1580 var idx;
1590 if ((idx = this.findIndex(project)) > -1) {
1600 if (project.errors != null) {
1610 return this.push("list." + idx + ".errors", err);
162 } else {
1630 return this.set("list." + idx + ".errors", [err]);
164 }
165 } else {
1660 throw 500;
167 }
168 },
169 demo: function() {
1700 return this.set({
171 'list': [
172 {
173 'owner': 'mbostock',
174 'name': 'd3'
175 }, {
176 'owner': 'medic',
177 'name': 'medic-webapp'
178 }, {
179 'owner': 'ractivejs',
180 'name': 'ractive'
181 }, {
182 'owner': 'radekstepan',
183 'name': 'disposable'
184 }, {
185 'owner': 'rails',
186 'name': 'rails'
187 }, {
188 'owner': 'rails',
189 'name': 'spring'
190 }
191 ],
192 'index': []
193 });
194 },
195 clear: function() {
1966 return this.set({
197 'list': [],
198 'index': []
199 });
200 },
201 sort: function(ref, data) {
20217 var i, idx, index, j, m, p, _i, _j, _len, _len1, _ref, _ref1;
20317 index = this.data.index || [];
20417 if (ref) {
20514 idx = sortedIndex(index, data, this.comparator());
20614 index.splice(idx, 0, ref);
207 } else {
2083 _ref = this.data.list;
2093 for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
2100 p = _ref[i];
2110 if (p.milestones == null) {
2120 continue;
213 }
2140 _ref1 = p.milestones;
2150 for (j = _j = 0, _len1 = _ref1.length; _j < _len1; j = ++_j) {
2160 m = _ref1[j];
2170 idx = sortedIndex(index, [p, m], this.comparator());
2180 index.splice(idx, 0, [i, j]);
219 }
220 }
221 }
22217 return this.set('index', index);
223 },
224 onconstruct: function() {
2251 this.subscribe('!projects/add', this.add, this);
2261 return this.subscribe('!projects/demo', this.demo, this);
227 },
228 onrender: function() {
2291 this.set('list', lscache.get('projects') || []);
2301 this.observe('list', function(projects) {
23126 return lscache.set('projects', _.pluckMany(projects, ['owner', 'name']));
232 }, {
233 'init': false
234 });
2351 return this.observe('sortBy', function() {
2363 this.set('index', null);
2373 return this.sort();
238 }, {
239 'init': false
240 });
241 }
242 });
243
244}).call(this);
245

/Users/radek/Dev/burnchart/src/models/user.coffee

100%
4
4
0
LineHitsSource
11(function() {
21 var Model;
3
41 Model = require('../utils/ractive/model.coffee');
5
61 module.exports = new Model({
7 'name': 'models/user',
8 'data': {
9 'uid': null
10 }
11 });
12
13}).call(this);
14

/Users/radek/Dev/burnchart/src/modules/chart/lines.coffee

93%
88
82
6
LineHitsSource
11(function() {
21 var config, d3, moment, _,
30 __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
4
51 _ = require('lodash');
6
71 d3 = require('d3');
8
91 moment = require('moment');
10
111 config = require('../../models/config.coffee');
12
131 module.exports = {
14 actual: function(issues, created_at, total) {
151 var head, max, min, range, rest;
161 head = [
17 {
18 'date': moment(created_at, moment.ISO_8601).toJSON(),
19 'points': total
20 }
21 ];
221 min = +Infinity;
231 max = -Infinity;
241 rest = _.map(issues, function(issue) {
253 var closed_at, size;
263 size = issue.size, closed_at = issue.closed_at;
273 if (size < min) {
283 min = size;
29 }
303 if (size > max) {
311 max = size;
32 }
333 issue.date = moment(closed_at, moment.ISO_8601).toJSON();
343 issue.points = total -= size;
353 return issue;
36 });
371 range = d3.scale.linear().domain([min, max]).range([5, 8]);
381 rest = _.map(rest, function(issue) {
393 issue.radius = range(issue.size);
403 return issue;
41 });
421 return [].concat(head, rest);
43 },
44 ideal: function(a, b, total) {
451 var days, length, now, once, velocity, _ref;
461 if (b < a) {
470 _ref = [a, b], b = _ref[0], a = _ref[1];
48 }
491 a = moment(a, moment.ISO_8601);
501 b = b != null ? moment(b, moment.ISO_8601) : moment.utc();
511 days = [];
521 length = 0;
531 (once = function(inc) {
543 var day, day_of;
553 day = a.add(1, 'days');
563 if (!(day_of = day.weekday())) {
571 day_of = 7;
58 }
593 if (__indexOf.call(config.data.chart.off_days, day_of) >= 0) {
600 days.push({
61 'date': day.toJSON(),
62 'off_day': true
63 });
64 } else {
653 length += 1;
663 days.push({
67 'date': day.toJSON()
68 });
69 }
703 if (!(day > b)) {
712 return once(inc + 1);
72 }
73 })(0);
741 velocity = total / (length - 1);
751 days = _.map(days, function(day, i) {
763 day.points = total;
773 if (days[i] && !days[i].off_day) {
783 total -= velocity;
79 }
803 return day;
81 });
821 if ((now = moment.utc()) > b) {
831 days.push({
84 'date': now.toJSON(),
85 'points': 0
86 });
87 }
881 return days;
89 },
90 trend: function(actual, created_at, due_on) {
911 var a, b, b1, c1, e, first, fn, intercept, l, last, now, slope, start, values;
921 if (!actual.length) {
930 return [];
94 }
951 first = actual[0], last = actual[actual.length - 1];
961 start = moment(first.date, moment.ISO_8601);
971 values = _.map(actual, function(_arg) {
983 var date, points;
993 date = _arg.date, points = _arg.points;
1003 return [moment(date, moment.ISO_8601).diff(start), points];
101 });
1021 now = moment.utc();
1031 values.push([now.diff(start), last.points]);
1041 b1 = 0;
1051 e = 0;
1061 c1 = 0;
1071 a = (l = values.length) * _.reduce(values, function(sum, _arg) {
1084 var a, b;
1094 a = _arg[0], b = _arg[1];
1104 b1 += a;
1114 e += b;
1124 c1 += Math.pow(a, 2);
1134 return sum + (a * b);
114 }, 0);
1151 slope = (a - (b1 * e)) / ((l * c1) - (Math.pow(b1, 2)));
1161 intercept = (e - (slope * b1)) / l;
1171 fn = function(x) {
1182 return slope * x + intercept;
119 };
1201 created_at = moment(created_at, moment.ISO_8601);
1211 if (due_on) {
1221 due_on = moment(due_on, moment.ISO_8601);
1231 if (now > due_on) {
1240 due_on = now;
125 }
126 } else {
1270 due_on = now;
128 }
1291 a = created_at.diff(start);
1301 b = due_on.diff(start);
1311 return [
132 {
133 'date': created_at.toJSON(),
134 'points': fn(a)
135 }, {
136 'date': due_on.toJSON(),
137 'points': fn(b)
138 }
139 ];
140 }
141 };
142
143}).call(this);
144

/Users/radek/Dev/burnchart/src/modules/github/issues.coffee

98%
53
52
1
LineHitsSource
11(function() {
21 var async, calcSize, config, oneStatus, request, _;
3
41 _ = require('lodash');
5
61 async = require('async');
7
81 config = require('../../models/config.coffee');
9
101 request = require('./request.coffee');
11
121 module.exports = {
13 fetchAll: function(repo, cb) {
1414 return async.parallel([_.partial(oneStatus, repo, 'open'), _.partial(oneStatus, repo, 'closed')], function(err, _arg) {
1514 var closed, open;
1614 open = _arg[0], closed = _arg[1];
1714 if (err == null) {
1813 err = null;
19 }
2014 return cb(err, {
21 open: open,
22 closed: closed
23 });
24 });
25 }
26 };
27
281 calcSize = function(list) {
2926 var issue, size, _i, _len;
3026 switch (config.data.chart.points) {
31 case 'ONE_SIZE':
3216 size = list.length;
3316 for (_i = 0, _len = list.length; _i < _len; _i++) {
341006 issue = list[_i];
351006 issue.size = 1;
36 }
3716 break;
38 case 'LABELS':
3910 size = 0;
4010 list = _.filter(list, function(issue) {
4116 var labels;
4216 if (!(labels = issue.labels)) {
432 return false;
44 }
4514 issue.size = _.reduce(labels, function(sum, label) {
4616 var matches;
4716 if (!(matches = label.name.match(config.data.chart.size_label))) {
486 return sum;
49 }
5010 return sum += parseInt(matches[1]);
51 }, 0);
5214 size += issue.size;
5314 return !!issue.size;
54 });
5510 break;
56 default:
570 throw 500;
58 }
5926 return {
60 list: list,
61 size: size
62 };
63 };
64
651 oneStatus = function(repo, state, cb) {
6628 var done, fetchPage, results;
6728 results = [];
6828 done = function(err) {
6928 if (err) {
702 return cb(err);
71 }
7226 return cb(null, calcSize(_.sortBy(results, 'closed_at')));
73 };
7428 return (fetchPage = function(page) {
7536 return request.allIssues(repo, {
76 state: state,
77 page: page
78 }, function(err, data) {
7936 if (err) {
802 return done(err);
81 }
8234 if (!data.length) {
836 return done(null, results);
84 }
8528 results = results.concat(data);
8628 if (data.length < 100) {
8720 return done(null, results);
88 }
898 return fetchPage(page + 1);
90 });
91 })(1);
92 };
93
94}).call(this);
95

/Users/radek/Dev/burnchart/src/modules/github/request.coffee

85%
125
107
18
LineHitsSource
12(function() {
22 var config, defaults, error, headers, isReady, isValid, mediator, ready, request, response, stack, superagent, user, _;
3
42 _ = require('lodash');
5
62 superagent = require('superagent');
7
82 require('../../utils/mixins.coffee');
9
102 config = require('../../models/config.coffee');
11
122 user = require('../../models/user.coffee');
13
142 mediator = require('../mediator.coffee');
15
162 superagent.parse = {
17 'application/json': function(res) {
180 var e;
190 try {
200 return JSON.parse(res);
21 } catch (_error) {
220 e = _error;
230 return {};
24 }
25 }
26 };
27
282 defaults = {
29 'github': {
30 'host': 'api.github.com',
31 'protocol': 'https'
32 }
33 };
34
352 module.exports = {
36 repo: function(_arg, cb) {
371 var name, owner;
381 owner = _arg.owner, name = _arg.name;
391 if (!isValid({
40 owner: owner,
41 name: name
42 })) {
430 return cb('Request is malformed');
44 }
451 return ready(function() {
461 var data, _ref;
471 data = _.defaults({
48 'path': "/repos/" + owner + "/" + name,
49 'headers': headers((_ref = user.data.github) != null ? _ref.accessToken : void 0)
50 }, defaults.github);
511 return request(data, cb);
52 });
53 },
54 allMilestones: function(_arg, cb) {
551 var name, owner;
561 owner = _arg.owner, name = _arg.name;
571 if (!isValid({
58 owner: owner,
59 name: name
60 })) {
610 return cb('Request is malformed');
62 }
631 return ready(function() {
641 var data, _ref;
651 data = _.defaults({
66 'path': "/repos/" + owner + "/" + name + "/milestones",
67 'query': {
68 'state': 'open',
69 'sort': 'due_date',
70 'direction': 'asc'
71 },
72 'headers': headers((_ref = user.data.github) != null ? _ref.accessToken : void 0)
73 }, defaults.github);
741 return request(data, cb);
75 });
76 },
77 oneMilestone: function(_arg, cb) {
784 var milestone, name, owner;
794 owner = _arg.owner, name = _arg.name, milestone = _arg.milestone;
804 if (!isValid({
81 owner: owner,
82 name: name,
83 milestone: milestone
84 })) {
850 return cb('Request is malformed');
86 }
874 return ready(function() {
884 var data, _ref;
894 data = _.defaults({
90 'path': "/repos/" + owner + "/" + name + "/milestones/" + milestone,
91 'query': {
92 'state': 'open',
93 'sort': 'due_date',
94 'direction': 'asc'
95 },
96 'headers': headers((_ref = user.data.github) != null ? _ref.accessToken : void 0)
97 }, defaults.github);
984 return request(data, cb);
99 });
100 },
101 allIssues: function(_arg, query, cb) {
1022 var milestone, name, owner;
1032 owner = _arg.owner, name = _arg.name, milestone = _arg.milestone;
1042 if (!isValid({
105 owner: owner,
106 name: name,
107 milestone: milestone
108 })) {
1090 return cb('Request is malformed');
110 }
1112 return ready(function() {
1122 var data, _ref;
1132 data = _.defaults({
114 'path': "/repos/" + owner + "/" + name + "/issues",
115 'query': _.extend(query, {
116 milestone: milestone,
117 'per_page': '100'
118 }),
119 'headers': headers((_ref = user.data.github) != null ? _ref.accessToken : void 0)
120 }, defaults.github);
1212 return request(data, cb);
122 });
123 }
124 };
125
1262 request = function(_arg, cb) {
1278 var exited, headers, host, k, path, protocol, q, query, req, timeout, v;
1288 protocol = _arg.protocol, host = _arg.host, path = _arg.path, query = _arg.query, headers = _arg.headers;
1298 exited = false;
1308 q = query ? '?' + ((function() {
1317 var _results;
1327 _results = [];
1337 for (k in query) {
13419 v = query[k];
13519 _results.push("" + k + "=" + v);
136 }
1377 return _results;
138 })()).join('&') : '';
1398 req = superagent.get("" + protocol + "://" + host + path + q);
1408 for (k in headers) {
14117 v = headers[k];
14217 req.set(k, v);
143 }
1448 timeout = setTimeout(function() {
1452 exited = true;
1462 return cb('Request has timed out');
147 }, config.data.request.timeout);
1488 return req.end(function(err, data) {
1497 if (exited) {
1501 return;
151 }
1526 exited = true;
1536 clearTimeout(timeout);
1546 return response(err, data, cb);
155 });
156 };
157
1582 response = function(err, data, cb) {
1596 if (err) {
1600 return cb(error(err));
161 }
1626 if (data.statusType !== 2) {
1633 return cb(error(data.body));
164 }
1653 return cb(null, data.body);
166 };
167
1682 headers = function(token) {
1698 var h;
1708 h = {
171 'Content-Type': 'application/json',
172 'Accept': 'application/vnd.github.v3'
173 };
1748 if (token != null) {
1751 h.Authorization = "token " + token;
176 }
1778 return h;
178 };
179
1802 isValid = function(obj) {
1818 var key, rules, val;
1828 rules = {
183 'owner': function(val) {
1848 return val != null;
185 },
186 'name': function(val) {
1878 return val != null;
188 },
189 'milestone': function(val) {
1906 return _.isInt(val);
191 }
192 };
1938 for (key in obj) {
19422 val = obj[key];
19522 if (key in rules && !rules[key](val)) {
1960 return false;
197 }
198 }
1998 return true;
200 };
201
2022 isReady = user.data.ready;
203
2042 stack = [];
205
2062 ready = function(cb) {
2078 if (isReady) {
2088 return cb();
209 } else {
2100 return stack.push(cb);
211 }
212 };
213
2142 user.observe('ready', function(val) {
2154 var _results;
2164 isReady = val;
2174 if (val) {
2182 _results = [];
2192 while (stack.length) {
2200 _results.push(stack.shift()());
221 }
2222 return _results;
223 }
224 });
225
2262 error = function(err) {
2273 var text, type;
2283 switch (false) {
229 case !_.isString(err):
2300 text = err;
2310 break;
232 case !_.isArray(err):
2330 text = err[1];
2340 break;
235 case !(_.isObject(err) && _.isString(err.message)):
2362 text = err.message;
237 }
2383 if (!text) {
2391 try {
2401 text = JSON.stringify(err);
241 } catch (_error) {
2420 text = err.toString();
243 }
244 }
2453 if (/API rate limit exceeded/.test(text)) {
2461 type = 'warn';
2471 mediator.fire('!app/notify', {
248 type: type,
249 text: text
250 });
251 }
2523 return text;
253 };
254
255}).call(this);
256

/Users/radek/Dev/burnchart/src/modules/mediator.coffee

100%
5
5
0
LineHitsSource
11(function() {
21 var Mediator, Ractive;
3
41 Ractive = require('ractive');
5
61 Mediator = Ractive.extend({});
7
81 module.exports = new Mediator();
9
10}).call(this);
11

/Users/radek/Dev/burnchart/src/modules/stats.coffee

97%
36
35
1
LineHitsSource
11(function() {
21 var moment, progress;
3
41 moment = require('moment');
5
61 progress = function(a, b) {
78 if (a + b === 0) {
80 return 0;
9 } else {
108 return 100 * (a / (b + a));
11 }
12 };
13
141 module.exports = function(milestone) {
1520 var a, b, c, days, isDone, isEmpty, isOnTime, isOverdue, points, time;
1620 if (milestone.stats != null) {
1714 return milestone.stats;
18 }
196 isDone = false;
206 isOnTime = true;
216 isOverdue = false;
226 isEmpty = true;
236 points = 0;
246 a = milestone.issues.closed.size;
256 b = milestone.issues.open.size;
266 if (a + b > 0) {
274 isEmpty = false;
284 points = progress(a, b);
294 if (points === 100) {
302 isDone = true;
31 }
32 }
336 if (milestone.due_on == null) {
342 return {
35 isOverdue: isOverdue,
36 isOnTime: isOnTime,
37 isDone: isDone,
38 isEmpty: isEmpty,
39 'progress': {
40 points: points
41 }
42 };
43 }
444 a = moment(milestone.created_at, moment.ISO_8601);
454 b = moment.utc();
464 c = moment(milestone.due_on, moment.ISO_8601);
474 if (b.isAfter(c) && !isDone) {
482 isOverdue = true;
49 }
504 time = progress(b.diff(a), c.diff(b));
514 days = (b.diff(a, 'days')) / 100;
524 isOnTime = points > time;
534 if (isDone) {
541 isOnTime = true;
55 }
564 return {
57 isDone: isDone,
58 days: days,
59 isOnTime: isOnTime,
60 isOverdue: isOverdue,
61 'progress': {
62 points: points,
63 time: time
64 }
65 };
66 };
67
68}).call(this);
69

/Users/radek/Dev/burnchart/src/utils/mixins.coffee

92%
13
12
1
LineHitsSource
11(function() {
21 var _;
3
41 _ = require('lodash');
5
61 _.mixin({
7 'pluckMany': function(source, keys) {
826 if (!_.isArray(keys)) {
90 throw '`keys` needs to be an Array';
10 }
1126 return _.map(source, function(item) {
1220 var obj;
1320 obj = {};
1420 _.each(keys, function(key) {
1540 return obj[key] = item[key];
16 });
1720 return obj;
18 });
19 },
20 'isInt': function(val) {
216 return !isNaN(val) && parseInt(Number(val)) === val && !isNaN(parseInt(val, 10));
22 }
23 });
24
25}).call(this);
26

/Users/radek/Dev/burnchart/src/utils/ractive/eventful.coffee

45%
24
11
13
LineHitsSource
11(function() {
21 var Ractive, mediator, _;
3
41 _ = require('lodash');
5
61 Ractive = require('ractive');
7
81 mediator = require('../../modules/mediator.coffee');
9
101 module.exports = Ractive.extend({
11 subscribe: function(name, cb, ctx) {
122 if (ctx == null) {
130 ctx = this;
14 }
152 if (!_.isArray(this._subs)) {
161 this._subs = [];
17 }
182 if (_.isFunction(cb)) {
192 return this._subs.push(mediator.on(name, _.bind(cb, ctx)));
20 } else {
210 return console.log("Warning: `cb` is not a function");
22 }
23 },
24 publish: function() {
250 return mediator.fire.apply(mediator, arguments);
26 },
27 onteardown: function() {
280 var sub, _i, _len, _ref, _results;
290 if (_.isArray(this._subs)) {
300 _ref = this._subs;
310 _results = [];
320 for (_i = 0, _len = _ref.length; _i < _len; _i++) {
330 sub = _ref[_i];
340 if (_.isFunction(sub.cancel)) {
350 _results.push(sub.cancel());
36 } else {
370 _results.push(console.log("Warning: `sub.cancel` is not a function"));
38 }
39 }
400 return _results;
41 }
42 }
43 });
44
45}).call(this);
46

/Users/radek/Dev/burnchart/src/utils/ractive/model.coffee

100%
9
9
0
LineHitsSource
11(function() {
21 var Eventful;
3
41 Eventful = require('./eventful.coffee');
5
61 module.exports = function(opts) {
73 var Model, model;
83 Model = Eventful.extend(opts);
93 model = new Model();
103 model.render();
113 return model;
12 };
13
14}).call(this);
15
\ No newline at end of file +

Coverage

86%
483
420
63

/Users/radek/Dev/burnchart/src/models/config.coffee

100%
4
4
0
LineHitsSource
11(function() {
21 var Model;
3
41 Model = require('../utils/ractive/model.coffee');
5
61 module.exports = new Model({
7 'name': 'models/config',
8 "data": {
9 "firebase": "burnchart",
10 "provider": "github",
11 "fields": {
12 "milestone": ["closed_issues", "created_at", "description", "due_on", "number", "open_issues", "title", "updated_at"]
13 },
14 "chart": {
15 "off_days": [],
16 "size_label": /^size (\d+)$/,
17 "points": 'ONE_SIZE'
18 },
19 "request": {
20 "timeout": 5e3
21 }
22 }
23 });
24
25}).call(this);
26

/Users/radek/Dev/burnchart/src/models/projects.coffee

80%
122
98
24
LineHitsSource
11(function() {
21 var Model, config, lscache, semver, sortedIndex, stats, user, _,
3 __slice = [].slice;
4
51 _ = require('lodash');
6
71 lscache = require('lscache');
8
91 sortedIndex = require('sortedindex-compare');
10
111 semver = require('semver');
12
131 Model = require('../utils/ractive/model.coffee');
14
151 config = require('../models/config.coffee');
16
171 stats = require('../modules/stats.coffee');
18
191 user = require('./user.coffee');
20
211 module.exports = new Model({
22 'name': 'models/projects',
23 'data': {
24 'sortBy': 'priority',
25 'sortFns': ['progress', 'priority', 'name']
26 },
27 comparator: function() {
2814 var deIdx, defaults, list, sortBy, _ref;
2914 _ref = this.data, list = _ref.list, sortBy = _ref.sortBy;
3014 deIdx = (function(_this) {
3114 return function(fn) {
3214 return function() {
3311 var i, j, rest, _arg;
3411 _arg = arguments[0], rest = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
3511 i = _arg[0], j = _arg[1];
3611 return fn.apply(_this, [[list[i], list[i].milestones[j]]].concat(rest));
37 };
38 };
39 })(this);
4014 defaults = function(arr, hash) {
417 var i, item, k, keys, p, ref, v, _i, _len, _results;
427 _results = [];
437 for (_i = 0, _len = arr.length; _i < _len; _i++) {
4414 item = arr[_i];
4514 _results.push((function() {
4614 var _results1;
4714 _results1 = [];
4814 for (k in hash) {
4926 v = hash[k];
5026 ref = item;
5126 _results1.push((function() {
5226 var _j, _len1, _ref1, _results2;
5326 _ref1 = keys = k.split('.');
5426 _results2 = [];
5526 for (i = _j = 0, _len1 = _ref1.length; _j < _len1; i = ++_j) {
5666 p = _ref1[i];
5766 if (i === keys.length - 1) {
5826 _results2.push(ref[p] != null ? ref[p] : ref[p] = v);
59 } else {
6040 _results2.push(ref = ref[p] != null ? ref[p] : ref[p] = {});
61 }
62 }
6326 return _results2;
64 })());
65 }
6614 return _results1;
67 })());
68 }
697 return _results;
70 };
7114 switch (sortBy) {
72 case 'progress':
732 return deIdx(function(_arg, _arg1) {
741 var aM, aP, bM, bP;
751 aP = _arg[0], aM = _arg[1];
761 bP = _arg1[0], bM = _arg1[1];
771 defaults([aM, bM], {
78 'stats.progress.points': 0
79 });
801 return aM.stats.progress.points - bM.stats.progress.points;
81 });
82 case 'priority':
837 return deIdx(function(_arg, _arg1) {
846 var $a, $b, aM, aP, bM, bP, _ref1;
856 aP = _arg[0], aM = _arg[1];
866 bP = _arg1[0], bM = _arg1[1];
876 defaults([aM, bM], {
88 'stats.progress.time': 0,
89 'stats.days': 1e3
90 });
916 _ref1 = _.map([aM, bM], function(_arg2) {
9212 var stats;
9312 stats = _arg2.stats;
9412 return (stats.progress.points - stats.progress.time) * stats.days;
95 }), $a = _ref1[0], $b = _ref1[1];
966 return $b - $a;
97 });
98 case 'name':
995 return deIdx(function(_arg, _arg1) {
1004 var aM, aP, bM, bP, name, owner;
1014 aP = _arg[0], aM = _arg[1];
1024 bP = _arg1[0], bM = _arg1[1];
1034 if (owner = bP.owner.localeCompare(aP.owner)) {
1040 return owner;
105 }
1064 if (name = bP.name.localeCompare(aP.name)) {
1070 return name;
108 }
1094 if (semver.valid(bM.title) && semver.valid(aM.title)) {
1101 return semver.gt(bM.title, aM.title);
111 } else {
1123 return bM.title.localeCompare(aM.title);
113 }
114 });
115 default:
1160 return function() {
1170 return 0;
118 };
119 }
120 },
121 find: function(project) {
1220 return _.find(this.data.list, project);
123 },
124 exists: function() {
1250 return !!this.find.apply(this, arguments);
126 },
127 add: function(project) {
1280 if (!this.exists(project)) {
1290 return this.push('list', project);
130 }
131 },
132 findIndex: function(_arg) {
13314 var name, owner;
13414 owner = _arg.owner, name = _arg.name;
13514 return _.findIndex(this.data.list, {
136 owner: owner,
137 name: name
138 });
139 },
140 addMilestone: function(project, milestone) {
14114 var i, j;
14214 _.extend(milestone, {
143 'stats': stats(milestone)
144 });
14514 if ((i = this.findIndex(project)) < 0) {
1460 throw 500;
147 }
14814 if (project.milestones != null) {
1498 this.push("list." + i + ".milestones", milestone);
1508 j = this.data.list[i].milestones.length - 1;
151 } else {
1526 this.set("list." + i + ".milestones", [milestone]);
1536 j = 0;
154 }
15514 return this.sort([i, j], [project, milestone]);
156 },
157 saveError: function(project, err) {
1580 var idx;
1590 if ((idx = this.findIndex(project)) > -1) {
1600 if (project.errors != null) {
1610 return this.push("list." + idx + ".errors", err);
162 } else {
1630 return this.set("list." + idx + ".errors", [err]);
164 }
165 } else {
1660 throw 500;
167 }
168 },
169 demo: function() {
1700 return this.set({
171 'list': [
172 {
173 'owner': 'mbostock',
174 'name': 'd3'
175 }, {
176 'owner': 'medic',
177 'name': 'medic-webapp'
178 }, {
179 'owner': 'ractivejs',
180 'name': 'ractive'
181 }, {
182 'owner': 'radekstepan',
183 'name': 'disposable'
184 }, {
185 'owner': 'rails',
186 'name': 'rails'
187 }, {
188 'owner': 'rails',
189 'name': 'spring'
190 }
191 ],
192 'index': []
193 });
194 },
195 clear: function() {
1966 return this.set({
197 'list': [],
198 'index': []
199 });
200 },
201 sort: function(ref, data) {
20217 var i, idx, index, j, m, p, _i, _j, _len, _len1, _ref, _ref1;
20317 index = this.data.index || [];
20417 if (ref) {
20514 idx = sortedIndex(index, data, this.comparator());
20614 index.splice(idx, 0, ref);
207 } else {
2083 _ref = this.data.list;
2093 for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
2100 p = _ref[i];
2110 if (p.milestones == null) {
2120 continue;
213 }
2140 _ref1 = p.milestones;
2150 for (j = _j = 0, _len1 = _ref1.length; _j < _len1; j = ++_j) {
2160 m = _ref1[j];
2170 idx = sortedIndex(index, [p, m], this.comparator());
2180 index.splice(idx, 0, [i, j]);
219 }
220 }
221 }
22217 return this.set('index', index);
223 },
224 onconstruct: function() {
2251 this.subscribe('!projects/add', this.add, this);
2261 return this.subscribe('!projects/demo', this.demo, this);
227 },
228 onrender: function() {
2291 this.set('list', lscache.get('projects') || []);
2301 this.observe('list', function(projects) {
23126 return lscache.set('projects', _.pluckMany(projects, ['owner', 'name']));
232 }, {
233 'init': false
234 });
2351 return this.observe('sortBy', function() {
2363 this.set('index', null);
2373 return this.sort();
238 }, {
239 'init': false
240 });
241 }
242 });
243
244}).call(this);
245

/Users/radek/Dev/burnchart/src/models/user.coffee

100%
4
4
0
LineHitsSource
11(function() {
21 var Model;
3
41 Model = require('../utils/ractive/model.coffee');
5
61 module.exports = new Model({
7 'name': 'models/user',
8 'data': {
9 'uid': null
10 }
11 });
12
13}).call(this);
14

/Users/radek/Dev/burnchart/src/modules/chart/lines.coffee

94%
88
83
5
LineHitsSource
11(function() {
21 var config, d3, moment, _,
30 __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
4
51 _ = require('lodash');
6
71 d3 = require('d3');
8
91 moment = require('moment');
10
111 config = require('../../models/config.coffee');
12
131 module.exports = {
14 actual: function(issues, created_at, total) {
151 var head, max, min, range, rest;
161 head = [
17 {
18 'date': moment(created_at, moment.ISO_8601).toJSON(),
19 'points': total
20 }
21 ];
221 min = +Infinity;
231 max = -Infinity;
241 rest = _.map(issues, function(issue) {
253 var closed_at, size;
263 size = issue.size, closed_at = issue.closed_at;
273 if (size < min) {
283 min = size;
29 }
303 if (size > max) {
311 max = size;
32 }
333 issue.date = moment(closed_at, moment.ISO_8601).toJSON();
343 issue.points = total -= size;
353 return issue;
36 });
371 range = d3.scale.linear().domain([min, max]).range([5, 8]);
381 rest = _.map(rest, function(issue) {
393 issue.radius = range(issue.size);
403 return issue;
41 });
421 return [].concat(head, rest);
43 },
44 ideal: function(a, b, total) {
451 var days, length, now, once, velocity, _ref;
461 if (b < a) {
470 _ref = [a, b], b = _ref[0], a = _ref[1];
48 }
491 a = moment(a, moment.ISO_8601);
501 b = b != null ? moment(b, moment.ISO_8601) : moment.utc();
511 days = [];
521 length = 0;
531 (once = function(inc) {
543 var day, day_of;
553 day = a.add(1, 'days');
563 if (!(day_of = day.weekday())) {
571 day_of = 7;
58 }
593 if (__indexOf.call(config.data.chart.off_days, day_of) >= 0) {
600 days.push({
61 'date': day.toJSON(),
62 'off_day': true
63 });
64 } else {
653 length += 1;
663 days.push({
67 'date': day.toJSON()
68 });
69 }
703 if (!(day > b)) {
712 return once(inc + 1);
72 }
73 })(0);
741 velocity = total / (length - 1);
751 days = _.map(days, function(day, i) {
763 day.points = total;
773 if (days[i] && !days[i].off_day) {
783 total -= velocity;
79 }
803 return day;
81 });
821 if ((now = moment.utc()) > b) {
831 days.push({
84 'date': now.toJSON(),
85 'points': 0
86 });
87 }
881 return days;
89 },
90 trend: function(actual, created_at, due_on) {
911 var a, b, b1, c1, e, first, fn, intercept, l, last, now, slope, start, values;
921 if (!actual.length) {
930 return [];
94 }
951 first = actual[0], last = actual[actual.length - 1];
961 start = moment(first.date, moment.ISO_8601);
971 values = _.map(actual, function(_arg) {
983 var date, points;
993 date = _arg.date, points = _arg.points;
1003 return [moment(date, moment.ISO_8601).diff(start), points];
101 });
1021 now = moment.utc();
1031 values.push([now.diff(start), last.points]);
1041 b1 = 0;
1051 e = 0;
1061 c1 = 0;
1071 a = (l = values.length) * _.reduce(values, function(sum, _arg) {
1084 var a, b;
1094 a = _arg[0], b = _arg[1];
1104 b1 += a;
1114 e += b;
1124 c1 += Math.pow(a, 2);
1134 return sum + (a * b);
114 }, 0);
1151 slope = (a - (b1 * e)) / ((l * c1) - (Math.pow(b1, 2)));
1161 intercept = (e - (slope * b1)) / l;
1171 fn = function(x) {
1182 return slope * x + intercept;
119 };
1201 created_at = moment(created_at, moment.ISO_8601);
1211 if (due_on) {
1221 due_on = moment(due_on, moment.ISO_8601);
1231 if (now > due_on) {
1241 due_on = now;
125 }
126 } else {
1270 due_on = now;
128 }
1291 a = created_at.diff(start);
1301 b = due_on.diff(start);
1311 return [
132 {
133 'date': created_at.toJSON(),
134 'points': fn(a)
135 }, {
136 'date': due_on.toJSON(),
137 'points': fn(b)
138 }
139 ];
140 }
141 };
142
143}).call(this);
144

/Users/radek/Dev/burnchart/src/modules/github/issues.coffee

98%
53
52
1
LineHitsSource
11(function() {
21 var async, calcSize, config, oneStatus, request, _;
3
41 _ = require('lodash');
5
61 async = require('async');
7
81 config = require('../../models/config.coffee');
9
101 request = require('./request.coffee');
11
121 module.exports = {
13 fetchAll: function(repo, cb) {
1414 return async.parallel([_.partial(oneStatus, repo, 'open'), _.partial(oneStatus, repo, 'closed')], function(err, _arg) {
1514 var closed, open;
1614 open = _arg[0], closed = _arg[1];
1714 if (err == null) {
1813 err = null;
19 }
2014 return cb(err, {
21 open: open,
22 closed: closed
23 });
24 });
25 }
26 };
27
281 calcSize = function(list) {
2926 var issue, size, _i, _len;
3026 switch (config.data.chart.points) {
31 case 'ONE_SIZE':
3216 size = list.length;
3316 for (_i = 0, _len = list.length; _i < _len; _i++) {
341006 issue = list[_i];
351006 issue.size = 1;
36 }
3716 break;
38 case 'LABELS':
3910 size = 0;
4010 list = _.filter(list, function(issue) {
4116 var labels;
4216 if (!(labels = issue.labels)) {
432 return false;
44 }
4514 issue.size = _.reduce(labels, function(sum, label) {
4616 var matches;
4716 if (!(matches = label.name.match(config.data.chart.size_label))) {
486 return sum;
49 }
5010 return sum += parseInt(matches[1]);
51 }, 0);
5214 size += issue.size;
5314 return !!issue.size;
54 });
5510 break;
56 default:
570 throw 500;
58 }
5926 return {
60 list: list,
61 size: size
62 };
63 };
64
651 oneStatus = function(repo, state, cb) {
6628 var done, fetchPage, results;
6728 results = [];
6828 done = function(err) {
6928 if (err) {
702 return cb(err);
71 }
7226 return cb(null, calcSize(_.sortBy(results, 'closed_at')));
73 };
7428 return (fetchPage = function(page) {
7536 return request.allIssues(repo, {
76 state: state,
77 page: page
78 }, function(err, data) {
7936 if (err) {
802 return done(err);
81 }
8234 if (!data.length) {
836 return done(null, results);
84 }
8528 results = results.concat(data);
8628 if (data.length < 100) {
8720 return done(null, results);
88 }
898 return fetchPage(page + 1);
90 });
91 })(1);
92 };
93
94}).call(this);
95

/Users/radek/Dev/burnchart/src/modules/github/request.coffee

85%
125
107
18
LineHitsSource
12(function() {
22 var config, defaults, error, headers, isReady, isValid, mediator, ready, request, response, stack, superagent, user, _;
3
42 _ = require('lodash');
5
62 superagent = require('superagent');
7
82 require('../../utils/mixins.coffee');
9
102 config = require('../../models/config.coffee');
11
122 user = require('../../models/user.coffee');
13
142 mediator = require('../mediator.coffee');
15
162 superagent.parse = {
17 'application/json': function(res) {
180 var e;
190 try {
200 return JSON.parse(res);
21 } catch (_error) {
220 e = _error;
230 return {};
24 }
25 }
26 };
27
282 defaults = {
29 'github': {
30 'host': 'api.github.com',
31 'protocol': 'https'
32 }
33 };
34
352 module.exports = {
36 repo: function(_arg, cb) {
371 var name, owner;
381 owner = _arg.owner, name = _arg.name;
391 if (!isValid({
40 owner: owner,
41 name: name
42 })) {
430 return cb('Request is malformed');
44 }
451 return ready(function() {
461 var data, _ref;
471 data = _.defaults({
48 'path': "/repos/" + owner + "/" + name,
49 'headers': headers((_ref = user.data.github) != null ? _ref.accessToken : void 0)
50 }, defaults.github);
511 return request(data, cb);
52 });
53 },
54 allMilestones: function(_arg, cb) {
551 var name, owner;
561 owner = _arg.owner, name = _arg.name;
571 if (!isValid({
58 owner: owner,
59 name: name
60 })) {
610 return cb('Request is malformed');
62 }
631 return ready(function() {
641 var data, _ref;
651 data = _.defaults({
66 'path': "/repos/" + owner + "/" + name + "/milestones",
67 'query': {
68 'state': 'open',
69 'sort': 'due_date',
70 'direction': 'asc'
71 },
72 'headers': headers((_ref = user.data.github) != null ? _ref.accessToken : void 0)
73 }, defaults.github);
741 return request(data, cb);
75 });
76 },
77 oneMilestone: function(_arg, cb) {
784 var milestone, name, owner;
794 owner = _arg.owner, name = _arg.name, milestone = _arg.milestone;
804 if (!isValid({
81 owner: owner,
82 name: name,
83 milestone: milestone
84 })) {
850 return cb('Request is malformed');
86 }
874 return ready(function() {
884 var data, _ref;
894 data = _.defaults({
90 'path': "/repos/" + owner + "/" + name + "/milestones/" + milestone,
91 'query': {
92 'state': 'open',
93 'sort': 'due_date',
94 'direction': 'asc'
95 },
96 'headers': headers((_ref = user.data.github) != null ? _ref.accessToken : void 0)
97 }, defaults.github);
984 return request(data, cb);
99 });
100 },
101 allIssues: function(_arg, query, cb) {
1022 var milestone, name, owner;
1032 owner = _arg.owner, name = _arg.name, milestone = _arg.milestone;
1042 if (!isValid({
105 owner: owner,
106 name: name,
107 milestone: milestone
108 })) {
1090 return cb('Request is malformed');
110 }
1112 return ready(function() {
1122 var data, _ref;
1132 data = _.defaults({
114 'path': "/repos/" + owner + "/" + name + "/issues",
115 'query': _.extend(query, {
116 milestone: milestone,
117 'per_page': '100'
118 }),
119 'headers': headers((_ref = user.data.github) != null ? _ref.accessToken : void 0)
120 }, defaults.github);
1212 return request(data, cb);
122 });
123 }
124 };
125
1262 request = function(_arg, cb) {
1278 var exited, headers, host, k, path, protocol, q, query, req, timeout, v;
1288 protocol = _arg.protocol, host = _arg.host, path = _arg.path, query = _arg.query, headers = _arg.headers;
1298 exited = false;
1308 q = query ? '?' + ((function() {
1317 var _results;
1327 _results = [];
1337 for (k in query) {
13419 v = query[k];
13519 _results.push("" + k + "=" + v);
136 }
1377 return _results;
138 })()).join('&') : '';
1398 req = superagent.get("" + protocol + "://" + host + path + q);
1408 for (k in headers) {
14117 v = headers[k];
14217 req.set(k, v);
143 }
1448 timeout = setTimeout(function() {
1452 exited = true;
1462 return cb('Request has timed out');
147 }, config.data.request.timeout);
1488 return req.end(function(err, data) {
1497 if (exited) {
1501 return;
151 }
1526 exited = true;
1536 clearTimeout(timeout);
1546 return response(err, data, cb);
155 });
156 };
157
1582 response = function(err, data, cb) {
1596 if (err) {
1600 return cb(error(err));
161 }
1626 if (data.statusType !== 2) {
1633 return cb(error(data.body));
164 }
1653 return cb(null, data.body);
166 };
167
1682 headers = function(token) {
1698 var h;
1708 h = {
171 'Content-Type': 'application/json',
172 'Accept': 'application/vnd.github.v3'
173 };
1748 if (token != null) {
1751 h.Authorization = "token " + token;
176 }
1778 return h;
178 };
179
1802 isValid = function(obj) {
1818 var key, rules, val;
1828 rules = {
183 'owner': function(val) {
1848 return val != null;
185 },
186 'name': function(val) {
1878 return val != null;
188 },
189 'milestone': function(val) {
1906 return _.isInt(val);
191 }
192 };
1938 for (key in obj) {
19422 val = obj[key];
19522 if (key in rules && !rules[key](val)) {
1960 return false;
197 }
198 }
1998 return true;
200 };
201
2022 isReady = user.data.ready;
203
2042 stack = [];
205
2062 ready = function(cb) {
2078 if (isReady) {
2088 return cb();
209 } else {
2100 return stack.push(cb);
211 }
212 };
213
2142 user.observe('ready', function(val) {
2154 var _results;
2164 isReady = val;
2174 if (val) {
2182 _results = [];
2192 while (stack.length) {
2200 _results.push(stack.shift()());
221 }
2222 return _results;
223 }
224 });
225
2262 error = function(err) {
2273 var text, type;
2283 switch (false) {
229 case !_.isString(err):
2300 text = err;
2310 break;
232 case !_.isArray(err):
2330 text = err[1];
2340 break;
235 case !(_.isObject(err) && _.isString(err.message)):
2362 text = err.message;
237 }
2383 if (!text) {
2391 try {
2401 text = JSON.stringify(err);
241 } catch (_error) {
2420 text = err.toString();
243 }
244 }
2453 if (/API rate limit exceeded/.test(text)) {
2461 type = 'warn';
2471 mediator.fire('!app/notify', {
248 type: type,
249 text: text
250 });
251 }
2523 return text;
253 };
254
255}).call(this);
256

/Users/radek/Dev/burnchart/src/modules/mediator.coffee

100%
5
5
0
LineHitsSource
11(function() {
21 var Mediator, Ractive;
3
41 Ractive = require('ractive');
5
61 Mediator = Ractive.extend({});
7
81 module.exports = new Mediator();
9
10}).call(this);
11

/Users/radek/Dev/burnchart/src/modules/stats.coffee

97%
36
35
1
LineHitsSource
11(function() {
21 var moment, progress;
3
41 moment = require('moment');
5
61 progress = function(a, b) {
78 if (a + b === 0) {
80 return 0;
9 } else {
108 return 100 * (a / (b + a));
11 }
12 };
13
141 module.exports = function(milestone) {
1520 var a, b, c, days, isDone, isEmpty, isOnTime, isOverdue, points, time;
1620 if (milestone.stats != null) {
1714 return milestone.stats;
18 }
196 isDone = false;
206 isOnTime = true;
216 isOverdue = false;
226 isEmpty = true;
236 points = 0;
246 a = milestone.issues.closed.size;
256 b = milestone.issues.open.size;
266 if (a + b > 0) {
274 isEmpty = false;
284 points = progress(a, b);
294 if (points === 100) {
302 isDone = true;
31 }
32 }
336 if (milestone.due_on == null) {
342 return {
35 isOverdue: isOverdue,
36 isOnTime: isOnTime,
37 isDone: isDone,
38 isEmpty: isEmpty,
39 'progress': {
40 points: points
41 }
42 };
43 }
444 a = moment(milestone.created_at, moment.ISO_8601);
454 b = moment.utc();
464 c = moment(milestone.due_on, moment.ISO_8601);
474 if (b.isAfter(c) && !isDone) {
482 isOverdue = true;
49 }
504 time = progress(b.diff(a), c.diff(b));
514 days = (b.diff(a, 'days')) / 100;
524 isOnTime = points > time;
534 if (isDone) {
541 isOnTime = true;
55 }
564 return {
57 isDone: isDone,
58 days: days,
59 isOnTime: isOnTime,
60 isOverdue: isOverdue,
61 'progress': {
62 points: points,
63 time: time
64 }
65 };
66 };
67
68}).call(this);
69

/Users/radek/Dev/burnchart/src/utils/mixins.coffee

92%
13
12
1
LineHitsSource
11(function() {
21 var _;
3
41 _ = require('lodash');
5
61 _.mixin({
7 'pluckMany': function(source, keys) {
826 if (!_.isArray(keys)) {
90 throw '`keys` needs to be an Array';
10 }
1126 return _.map(source, function(item) {
1220 var obj;
1320 obj = {};
1420 _.each(keys, function(key) {
1540 return obj[key] = item[key];
16 });
1720 return obj;
18 });
19 },
20 'isInt': function(val) {
216 return !isNaN(val) && parseInt(Number(val)) === val && !isNaN(parseInt(val, 10));
22 }
23 });
24
25}).call(this);
26

/Users/radek/Dev/burnchart/src/utils/ractive/eventful.coffee

45%
24
11
13
LineHitsSource
11(function() {
21 var Ractive, mediator, _;
3
41 _ = require('lodash');
5
61 Ractive = require('ractive');
7
81 mediator = require('../../modules/mediator.coffee');
9
101 module.exports = Ractive.extend({
11 subscribe: function(name, cb, ctx) {
122 if (ctx == null) {
130 ctx = this;
14 }
152 if (!_.isArray(this._subs)) {
161 this._subs = [];
17 }
182 if (_.isFunction(cb)) {
192 return this._subs.push(mediator.on(name, _.bind(cb, ctx)));
20 } else {
210 return console.log("Warning: `cb` is not a function");
22 }
23 },
24 publish: function() {
250 return mediator.fire.apply(mediator, arguments);
26 },
27 onteardown: function() {
280 var sub, _i, _len, _ref, _results;
290 if (_.isArray(this._subs)) {
300 _ref = this._subs;
310 _results = [];
320 for (_i = 0, _len = _ref.length; _i < _len; _i++) {
330 sub = _ref[_i];
340 if (_.isFunction(sub.cancel)) {
350 _results.push(sub.cancel());
36 } else {
370 _results.push(console.log("Warning: `sub.cancel` is not a function"));
38 }
39 }
400 return _results;
41 }
42 }
43 });
44
45}).call(this);
46

/Users/radek/Dev/burnchart/src/utils/ractive/model.coffee

100%
9
9
0
LineHitsSource
11(function() {
21 var Eventful;
3
41 Eventful = require('./eventful.coffee');
5
61 module.exports = function(opts) {
73 var Model, model;
83 Model = Eventful.extend(opts);
93 model = new Model();
103 model.render();
113 return model;
12 };
13
14}).call(this);
15
\ No newline at end of file diff --git a/package.json b/package.json index ca12124..62c8b9e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "burnchart", - "version": "2.0.4", + "version": "2.0.5", "description": "GitHub Burndown Chart as a Service", "author": "Radek Stepan (http://radekstepan.com)", "license": "AGPL-3.0", @@ -19,6 +19,9 @@ "start": "rake serve", "test": "rake test" }, + "bin": { + "burnchart": "./bin/run.js" + }, "dependencies": { "async": "~0.9.0", "brain": "^0.7.0", @@ -31,6 +34,7 @@ "lscache": "~1.0.2", "marked": "~0.3.2", "moment": "~2.8.3", + "node-static": "~0.7.6", "normalize.less": "^1.0.0", "ractive": "~0.6.1", "ractive-ractive": "~0.4.4", @@ -57,7 +61,6 @@ "lesshat": "~3.0.2", "mocha": "~2.0.1", "mocha-lcov-reporter": "0.0.1", - "node-static": "~0.7.6", "proxyquire": "^1.3.0", "ractivate": "~0.2.0", "watch": "^0.13.0", @@ -79,4 +82,4 @@ } } } -} +} \ No newline at end of file diff --git a/test/ractive.coffee b/test/ractive.coffee index 210da0d..cb2e83e 100644 --- a/test/ractive.coffee +++ b/test/ractive.coffee @@ -20,7 +20,7 @@ module.exports = -> # Need to deal with multiple teardown handlers do this._super - do view.render() + do view.render view.publish '!event' assert ctx.called, 1