diff --git a/docs/COVERAGE.html b/docs/COVERAGE.html index 3cf7dc2..3e87d1c 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

48%
474
228
246

/home/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 "datetime": /^(\d{4}-\d{2}-\d{2})T(.*)/,
17 "size_label": /^size (\d+)$/,
18 "location": /^#!((\/[^\/]+){2,3})$/,
19 "points": 'ONE_SIZE'
20 },
21 "request": {
22 "timeout": 5e3
23 }
24 }
25 });
26
27}).call(this);
28

/home/radek/dev/burnchart/src/models/projects.coffee

22%
122
28
94
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() {
280 var deIdx, defaults, list, sortBy, _ref;
290 _ref = this.data, list = _ref.list, sortBy = _ref.sortBy;
300 deIdx = (function(_this) {
310 return function(fn) {
320 return function() {
330 var i, j, rest, _arg;
340 _arg = arguments[0], rest = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
350 i = _arg[0], j = _arg[1];
360 return fn.apply(_this, [[list[i], list[i].milestones[j]]].concat(rest));
37 };
38 };
39 })(this);
400 defaults = function(arr, hash) {
410 var i, item, k, keys, p, ref, v, _i, _len, _results;
420 _results = [];
430 for (_i = 0, _len = arr.length; _i < _len; _i++) {
440 item = arr[_i];
450 _results.push((function() {
460 var _results1;
470 _results1 = [];
480 for (k in hash) {
490 v = hash[k];
500 ref = item;
510 _results1.push((function() {
520 var _j, _len1, _ref1, _results2;
530 _ref1 = keys = k.split('.');
540 _results2 = [];
550 for (i = _j = 0, _len1 = _ref1.length; _j < _len1; i = ++_j) {
560 p = _ref1[i];
570 if (i === keys.length - 1) {
580 _results2.push(ref[p] != null ? ref[p] : ref[p] = v);
59 } else {
600 _results2.push(ref = ref[p] != null ? ref[p] : ref[p] = {});
61 }
62 }
630 return _results2;
64 })());
65 }
660 return _results1;
67 })());
68 }
690 return _results;
70 };
710 switch (sortBy) {
72 case 'progress':
730 return deIdx(function(_arg, _arg1) {
740 var aM, aP, bM, bP;
750 aP = _arg[0], aM = _arg[1];
760 bP = _arg1[0], bM = _arg1[1];
770 defaults([aM, bM], {
78 'stats.progress.points': 0
79 });
800 return aM.stats.progress.points - bM.stats.progress.points;
81 });
82 case 'priority':
830 return deIdx(function(_arg, _arg1) {
840 var $a, $b, aM, aP, bM, bP, _ref1;
850 aP = _arg[0], aM = _arg[1];
860 bP = _arg1[0], bM = _arg1[1];
870 defaults([aM, bM], {
88 'stats.progress.time': 0,
89 'stats.days': 1e3
90 });
910 _ref1 = _.map([aM, bM], function(_arg2) {
920 var stats;
930 stats = _arg2.stats;
940 return (stats.progress.points - stats.progress.time) * stats.days;
95 }), $a = _ref1[0], $b = _ref1[1];
960 return $b - $a;
97 });
98 case 'name':
990 return deIdx(function(_arg, _arg1) {
1000 var aM, aP, bM, bP, name, owner;
1010 aP = _arg[0], aM = _arg[1];
1020 bP = _arg1[0], bM = _arg1[1];
1030 if (owner = bP.owner.localeCompare(aP.owner)) {
1040 return owner;
105 }
1060 if (name = bP.name.localeCompare(aP.name)) {
1070 return name;
108 }
1090 if (semver.valid(bM.title) && semver.valid(aM.title)) {
1100 return semver.gt(bM.title, aM.title);
111 } else {
1120 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) {
1330 var name, owner;
1340 owner = _arg.owner, name = _arg.name;
1350 return _.findIndex(this.data.list, {
136 owner: owner,
137 name: name
138 });
139 },
140 addMilestone: function(project, milestone) {
1416 var i, j;
1426 _.extend(milestone, {
143 'stats': stats(milestone)
144 });
1450 if ((i = this.findIndex(project)) < 0) {
1460 throw 500;
147 }
1480 if (project.milestones != null) {
1490 this.push("list." + i + ".milestones", milestone);
1500 j = this.data.list[i].milestones.length - 1;
151 } else {
1520 this.set("list." + i + ".milestones", [milestone]);
1530 j = 0;
154 }
1550 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) {
2023 var i, idx, index, j, m, p, _i, _j, _len, _len1, _ref, _ref1;
2033 index = this.data.index || [];
2043 if (ref) {
2050 idx = sortedIndex(index, data, this.comparator());
2060 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 }
2223 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) {
23112 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

/home/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 });
9
10}).call(this);
11

/home/radek/dev/burnchart/src/modules/chart/lines.coffee

6%
88
6
82
LineHitsSource
11(function() {
21 var config, d3, _,
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 config = require('../../models/config.coffee');
10
111 module.exports = {
12 actual: function(issues, created_at, total) {
130 var head, max, min, range, rest;
140 head = [
15 {
16 'date': new Date(created_at),
17 'points': total
18 }
19 ];
200 min = +Infinity;
210 max = -Infinity;
220 rest = _.map(issues, function(issue) {
230 var closed_at, size;
240 size = issue.size, closed_at = issue.closed_at;
250 if (size < min) {
260 min = size;
27 }
280 if (size > max) {
290 max = size;
30 }
310 issue.date = new Date(closed_at);
320 issue.points = total -= size;
330 return issue;
34 });
350 range = d3.scale.linear().domain([min, max]).range([5, 8]);
360 rest = _.map(rest, function(issue) {
370 issue.radius = range(issue.size);
380 return issue;
39 });
400 return [].concat(head, rest);
41 },
42 ideal: function(a, b, total) {
430 var cutoff, d, days, length, m, now, once, velocity, y, _ref, _ref1;
440 if (b < a) {
450 _ref = [a, b], b = _ref[0], a = _ref[1];
46 }
470 _ref1 = _.map(a.match(config.data.chart.datetime)[1].split('-'), function(v) {
480 return parseInt(v);
49 }), y = _ref1[0], m = _ref1[1], d = _ref1[2];
500 cutoff = new Date(b);
510 days = [];
520 length = 0;
530 (once = function(inc) {
540 var day, day_of;
550 day = new Date(y, m - 1, d + inc);
560 if (!(day_of = day.getDay())) {
570 day_of = 7;
58 }
590 if (__indexOf.call(config.data.chart.off_days, day_of) >= 0) {
600 days.push({
61 date: day,
62 off_day: true
63 });
64 } else {
650 length += 1;
660 days.push({
67 date: day
68 });
69 }
700 if (!(day > cutoff)) {
710 return once(inc + 1);
72 }
73 })(0);
740 velocity = total / (length - 1);
750 days = _.map(days, function(day, i) {
760 day.points = total;
770 if (days[i] && !days[i].off_day) {
780 total -= velocity;
79 }
800 return day;
81 });
820 if ((now = new Date) > cutoff) {
830 days.push({
84 date: now,
85 points: 0
86 });
87 }
880 return days;
89 },
90 trend: function(actual, created_at, due_on) {
910 var a, b, b1, c1, e, fn, intercept, l, last, now, slope, start, values;
920 if (!actual.length) {
930 return [];
94 }
950 start = +actual[0].date;
960 values = _.map(actual, function(_arg) {
970 var date, points;
980 date = _arg.date, points = _arg.points;
990 return [+date - start, points];
100 });
1010 last = actual[actual.length - 1];
1020 values.push([+(new Date) - start, last.points]);
1030 b1 = 0;
1040 e = 0;
1050 c1 = 0;
1060 a = (l = values.length) * _.reduce(values, function(sum, _arg) {
1070 var a, b;
1080 a = _arg[0], b = _arg[1];
1090 b1 += a;
1100 e += b;
1110 c1 += Math.pow(a, 2);
1120 return sum + (a * b);
113 }, 0);
1140 slope = (a - (b1 * e)) / ((l * c1) - (Math.pow(b1, 2)));
1150 intercept = (e - (slope * b1)) / l;
1160 fn = function(x) {
1170 return slope * x + intercept;
118 };
1190 created_at = new Date(created_at);
1200 now = new Date;
1210 if (due_on) {
1220 due_on = new Date(due_on);
1230 if (now > due_on) {
1240 due_on = now;
125 }
126 } else {
1270 due_on = now;
128 }
1290 a = created_at - start;
1300 b = due_on - start;
1310 return [
132 {
133 'date': created_at,
134 'points': fn(a)
135 }, {
136 'date': due_on,
137 'points': fn(b)
138 }
139 ];
140 }
141 };
142
143}).call(this);
144

/home/radek/dev/burnchart/src/modules/github/issues.coffee

44%
49
22
27
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) {
1517 var closed, open;
1617 open = _arg[0], closed = _arg[1];
1717 return cb(err, {
18 open: open,
19 closed: closed
20 });
21 });
22 }
23 };
24
251 calcSize = function(list) {
260 var issue, size, _i, _len;
270 switch (config.data.chart.points) {
28 case 'ONE_SIZE':
290 size = list.length;
300 for (_i = 0, _len = list.length; _i < _len; _i++) {
310 issue = list[_i];
320 issue.size = 1;
33 }
340 break;
35 case 'LABELS':
360 size = 0;
370 list = _.filter(list, function(issue) {
380 var labels;
390 if (!(labels = issue.labels)) {
400 return false;
41 }
420 issue.size = _.reduce(labels, function(sum, label) {
430 var matches;
440 if (!(matches = label.name.match(config.data.chart.size_label))) {
450 return sum;
46 }
470 return sum += parseInt(matches[1]);
48 }, 0);
490 size += issue.size;
500 return !!issue.size;
51 });
52 }
530 return {
54 list: list,
55 size: size
56 };
57 };
58
591 oneStatus = function(repo, state, cb) {
6028 var done, fetchPage, results;
6128 results = [];
6228 done = function(err) {
6317 if (err) {
6417 return cb(err);
65 }
660 return cb(null, calcSize(results));
67 };
6828 return (fetchPage = function(page) {
6928 return request.allIssues(repo, {
70 state: state,
71 page: page
72 }, function(err, data) {
7317 if (err) {
7417 return done(err);
75 }
760 if (!data.length) {
770 return done(null, results);
78 }
790 results = results.concat(_.sortBy(data, 'closed_at'));
800 if (data.length < 100) {
810 return done(null, results);
82 }
830 return fetchPage(page + 1);
84 });
85 })(1);
86 };
87
88}).call(this);
89

/home/radek/dev/burnchart/src/modules/github/request.coffee

77%
124
96
28
LineHitsSource
12(function() {
22 var config, defaults, error, headers, isReady, isValid, 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 superagent.parse = {
15 'application/json': function(res) {
1617 var e;
1717 try {
1817 return JSON.parse(res);
19 } catch (_error) {
2017 e = _error;
2117 return {};
22 }
23 }
24 };
25
262 defaults = {
27 'github': {
28 'host': 'api.github.com',
29 'protocol': 'https'
30 }
31 };
32
332 module.exports = {
34 repo: function(_arg, cb) {
350 var name, owner;
360 owner = _arg.owner, name = _arg.name;
370 if (!isValid({
38 owner: owner,
39 name: name
40 })) {
410 return cb('Request is malformed');
42 }
430 return ready(function() {
440 var data;
450 data = _.defaults({
46 'path': "/repos/" + owner + "/" + name,
47 'headers': headers(user.data.accessToken)
48 }, defaults.github);
490 return request(data, cb);
50 });
51 },
52 allMilestones: function(_arg, cb) {
531 var name, owner;
541 owner = _arg.owner, name = _arg.name;
551 if (!isValid({
56 owner: owner,
57 name: name
58 })) {
590 return cb('Request is malformed');
60 }
611 return ready(function() {
621 var data;
631 data = _.defaults({
64 'path': "/repos/" + owner + "/" + name + "/milestones",
65 'query': {
66 'state': 'open',
67 'sort': 'due_date',
68 'direction': 'asc'
69 },
70 'headers': headers(user.data.accessToken)
71 }, defaults.github);
721 return request(data, cb);
73 });
74 },
75 oneMilestone: function(_arg, cb) {
763 var milestone, name, owner;
773 owner = _arg.owner, name = _arg.name, milestone = _arg.milestone;
783 if (!isValid({
79 owner: owner,
80 name: name,
81 milestone: milestone
82 })) {
830 return cb('Request is malformed');
84 }
853 return ready(function() {
863 var data;
873 data = _.defaults({
88 'path': "/repos/" + owner + "/" + name + "/milestones/" + milestone,
89 'query': {
90 'state': 'open',
91 'sort': 'due_date',
92 'direction': 'asc'
93 },
94 'headers': headers(user.data.accessToken)
95 }, defaults.github);
963 return request(data, cb);
97 });
98 },
99 allIssues: function(_arg, query, cb) {
10030 var milestone, name, owner;
10130 owner = _arg.owner, name = _arg.name, milestone = _arg.milestone;
10230 if (!isValid({
103 owner: owner,
104 name: name,
105 milestone: milestone
106 })) {
1070 return cb('Request is malformed');
108 }
10930 return ready(function() {
11030 var data;
11130 data = _.defaults({
112 'path': "/repos/" + owner + "/" + name + "/issues",
113 'query': _.extend(query, {
114 milestone: milestone,
115 'per_page': '100'
116 }),
117 'headers': headers(user.data.accessToken)
118 }, defaults.github);
11930 return request(data, cb);
120 });
121 }
122 };
123
1242 request = function(_arg, cb) {
12534 var exited, headers, host, k, path, protocol, q, query, req, timeout, v;
12634 protocol = _arg.protocol, host = _arg.host, path = _arg.path, query = _arg.query, headers = _arg.headers;
12734 exited = false;
12834 q = query ? '?' + ((function() {
12934 var _results;
13034 _results = [];
13134 for (k in query) {
132128 v = query[k];
133128 _results.push("" + k + "=" + v);
134 }
13534 return _results;
136 })()).join('&') : '';
13734 req = superagent.get("" + protocol + "://" + host + path + q);
13834 for (k in headers) {
13968 v = headers[k];
14068 req.set(k, v);
141 }
14234 timeout = setTimeout(function() {
1431 exited = true;
1441 return cb('Request has timed out');
145 }, config.data.request.timeout);
14634 return req.end(function(err, data) {
14723 if (exited) {
1481 return;
149 }
15022 exited = true;
15122 clearTimeout(timeout);
15222 return response(err, data, cb);
153 });
154 };
155
1562 response = function(err, data, cb) {
15722 var _ref;
15822 if (err) {
1590 return cb(error(err));
160 }
16122 if (data.statusType !== 2) {
16219 if ((data != null ? (_ref = data.body) != null ? _ref.message : void 0 : void 0) != null) {
1631 return cb(data.body.message);
164 }
16518 return cb(data.error.message);
166 }
1673 return cb(null, data.body);
168 };
169
1702 headers = function(token) {
17134 var h;
17234 h = {
173 'Content-Type': 'application/json',
174 'Accept': 'application/vnd.github.v3'
175 };
17634 if (token != null) {
1770 h.Authorization = "token " + token;
178 }
17934 return h;
180 };
181
1822 isValid = function(obj) {
18334 var key, rules, val;
18434 rules = {
185 'owner': function(val) {
18634 return val != null;
187 },
188 'name': function(val) {
18934 return val != null;
190 },
191 'milestone': function(val) {
19233 return _.isInt(val);
193 }
194 };
19534 for (key in obj) {
196101 val = obj[key];
197101 if (key in rules && !rules[key](val)) {
1980 return false;
199 }
200 }
20134 return true;
202 };
203
2042 isReady = user.data.ready;
205
2062 stack = [];
207
2082 ready = function(cb) {
20934 if (isReady) {
21034 return cb();
211 } else {
2120 return stack.push(cb);
213 }
214 };
215
2162 user.observe('ready', function(val) {
2174 var _results;
2184 isReady = val;
2194 if (val) {
2202 _results = [];
2212 while (stack.length) {
2220 _results.push(stack.shift()());
223 }
2242 return _results;
225 }
226 });
227
2282 error = function(err) {
2290 var message;
2300 switch (false) {
231 case !_.isString(err):
2320 message = err;
2330 break;
234 case !_.isArray(err):
2350 message = err[1];
2360 break;
237 case !(_.isObject(err) && _.isString(err.message)):
2380 message = err.message;
239 }
2400 if (!message) {
2410 try {
2420 message = JSON.stringify(err);
243 } catch (_error) {
2440 message = err.toString();
245 }
246 }
2470 return message;
248 };
249
250}).call(this);
251

/home/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

/home/radek/dev/burnchart/src/modules/stats.coffee

96%
32
31
1
LineHitsSource
11(function() {
21 var moment, progress;
3
41 moment = require('moment');
5
61 progress = function(a, b) {
76 if (a + b === 0) {
80 return 0;
9 } else {
106 return 100 * (a / (b + a));
11 }
12 };
13
141 module.exports = function(milestone) {
1511 var a, b, c, days, isDone, isEmpty, isOnTime, isOverdue, points, time;
1611 isDone = false;
1711 isOnTime = true;
1811 isOverdue = false;
1911 isEmpty = true;
2011 points = 0;
2111 a = milestone.issues.closed.size;
225 b = milestone.issues.open.size;
235 if (a + b > 0) {
243 isEmpty = false;
253 points = progress(a, b);
263 if (points === 100) {
271 isDone = true;
28 }
29 }
305 if (milestone.due_on == null) {
312 return {
32 isOverdue: isOverdue,
33 isOnTime: isOnTime,
34 isDone: isDone,
35 isEmpty: isEmpty,
36 'progress': {
37 points: points
38 }
39 };
40 }
413 a = +new Date(milestone.created_at);
423 b = +(new Date);
433 c = +new Date(milestone.due_on);
443 if (b > c) {
451 isOverdue = true;
46 }
473 time = progress(b - a, c - b);
483 days = (moment(b).diff(moment(a), 'days')) / 100;
493 isOnTime = points > time;
503 return {
51 isDone: isDone,
52 days: days,
53 isOnTime: isOnTime,
54 isOverdue: isOverdue,
55 'progress': {
56 points: points,
57 time: time
58 }
59 };
60 };
61
62}).call(this);
63

/home/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) {
812 if (!_.isArray(keys)) {
90 throw '`keys` needs to be an Array';
10 }
1112 return _.map(source, function(item) {
126 var obj;
136 obj = {};
146 _.each(keys, function(key) {
1512 return obj[key] = item[key];
16 });
176 return obj;
18 });
19 },
20 'isInt': function(val) {
2133 return !isNaN(val) && parseInt(Number(val)) === val && !isNaN(parseInt(val, 10));
22 }
23 });
24
25}).call(this);
26

/home/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

/home/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

67%
478
323
155

/home/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 "datetime": /^(\d{4}-\d{2}-\d{2})T(.*)/,
17 "size_label": /^size (\d+)$/,
18 "location": /^#!((\/[^\/]+){2,3})$/,
19 "points": 'ONE_SIZE'
20 },
21 "request": {
22 "timeout": 5e3
23 }
24 }
25 });
26
27}).call(this);
28

/home/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

/home/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 });
9
10}).call(this);
11

/home/radek/dev/burnchart/src/modules/chart/lines.coffee

6%
88
6
82
LineHitsSource
11(function() {
21 var config, d3, _,
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 config = require('../../models/config.coffee');
10
111 module.exports = {
12 actual: function(issues, created_at, total) {
130 var head, max, min, range, rest;
140 head = [
15 {
16 'date': new Date(created_at),
17 'points': total
18 }
19 ];
200 min = +Infinity;
210 max = -Infinity;
220 rest = _.map(issues, function(issue) {
230 var closed_at, size;
240 size = issue.size, closed_at = issue.closed_at;
250 if (size < min) {
260 min = size;
27 }
280 if (size > max) {
290 max = size;
30 }
310 issue.date = new Date(closed_at);
320 issue.points = total -= size;
330 return issue;
34 });
350 range = d3.scale.linear().domain([min, max]).range([5, 8]);
360 rest = _.map(rest, function(issue) {
370 issue.radius = range(issue.size);
380 return issue;
39 });
400 return [].concat(head, rest);
41 },
42 ideal: function(a, b, total) {
430 var cutoff, d, days, length, m, now, once, velocity, y, _ref, _ref1;
440 if (b < a) {
450 _ref = [a, b], b = _ref[0], a = _ref[1];
46 }
470 _ref1 = _.map(a.match(config.data.chart.datetime)[1].split('-'), function(v) {
480 return parseInt(v);
49 }), y = _ref1[0], m = _ref1[1], d = _ref1[2];
500 cutoff = new Date(b);
510 days = [];
520 length = 0;
530 (once = function(inc) {
540 var day, day_of;
550 day = new Date(y, m - 1, d + inc);
560 if (!(day_of = day.getDay())) {
570 day_of = 7;
58 }
590 if (__indexOf.call(config.data.chart.off_days, day_of) >= 0) {
600 days.push({
61 date: day,
62 off_day: true
63 });
64 } else {
650 length += 1;
660 days.push({
67 date: day
68 });
69 }
700 if (!(day > cutoff)) {
710 return once(inc + 1);
72 }
73 })(0);
740 velocity = total / (length - 1);
750 days = _.map(days, function(day, i) {
760 day.points = total;
770 if (days[i] && !days[i].off_day) {
780 total -= velocity;
79 }
800 return day;
81 });
820 if ((now = new Date) > cutoff) {
830 days.push({
84 date: now,
85 points: 0
86 });
87 }
880 return days;
89 },
90 trend: function(actual, created_at, due_on) {
910 var a, b, b1, c1, e, fn, intercept, l, last, now, slope, start, values;
920 if (!actual.length) {
930 return [];
94 }
950 start = +actual[0].date;
960 values = _.map(actual, function(_arg) {
970 var date, points;
980 date = _arg.date, points = _arg.points;
990 return [+date - start, points];
100 });
1010 last = actual[actual.length - 1];
1020 values.push([+(new Date) - start, last.points]);
1030 b1 = 0;
1040 e = 0;
1050 c1 = 0;
1060 a = (l = values.length) * _.reduce(values, function(sum, _arg) {
1070 var a, b;
1080 a = _arg[0], b = _arg[1];
1090 b1 += a;
1100 e += b;
1110 c1 += Math.pow(a, 2);
1120 return sum + (a * b);
113 }, 0);
1140 slope = (a - (b1 * e)) / ((l * c1) - (Math.pow(b1, 2)));
1150 intercept = (e - (slope * b1)) / l;
1160 fn = function(x) {
1170 return slope * x + intercept;
118 };
1190 created_at = new Date(created_at);
1200 now = new Date;
1210 if (due_on) {
1220 due_on = new Date(due_on);
1230 if (now > due_on) {
1240 due_on = now;
125 }
126 } else {
1270 due_on = now;
128 }
1290 a = created_at - start;
1300 b = due_on - start;
1310 return [
132 {
133 'date': created_at,
134 'points': fn(a)
135 }, {
136 'date': due_on,
137 'points': fn(b)
138 }
139 ];
140 }
141 };
142
143}).call(this);
144

/home/radek/dev/burnchart/src/modules/github/issues.coffee

98%
51
50
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 return cb(err, {
18 open: open,
19 closed: closed
20 });
21 });
22 }
23 };
24
251 calcSize = function(list) {
2626 var issue, size, _i, _len;
2726 switch (config.data.chart.points) {
28 case 'ONE_SIZE':
2916 size = list.length;
3016 for (_i = 0, _len = list.length; _i < _len; _i++) {
311006 issue = list[_i];
321006 issue.size = 1;
33 }
3416 break;
35 case 'LABELS':
3610 size = 0;
3710 list = _.filter(list, function(issue) {
3816 var labels;
3916 if (!(labels = issue.labels)) {
402 return false;
41 }
4214 issue.size = _.reduce(labels, function(sum, label) {
4316 var matches;
4416 if (!(matches = label.name.match(config.data.chart.size_label))) {
456 return sum;
46 }
4710 return sum += parseInt(matches[1]);
48 }, 0);
4914 size += issue.size;
5014 return !!issue.size;
51 });
5210 break;
53 default:
540 throw 500;
55 }
5626 return {
57 list: list,
58 size: size
59 };
60 };
61
621 oneStatus = function(repo, state, cb) {
6328 var done, fetchPage, results;
6428 results = [];
6528 done = function(err) {
6628 if (err) {
672 return cb(err);
68 }
6926 return cb(null, calcSize(results));
70 };
7128 return (fetchPage = function(page) {
7236 return request.allIssues(repo, {
73 state: state,
74 page: page
75 }, function(err, data) {
7636 if (err) {
772 return done(err);
78 }
7934 if (!data.length) {
806 return done(null, results);
81 }
8228 results = results.concat(_.sortBy(data, 'closed_at'));
8328 if (data.length < 100) {
8420 return done(null, results);
85 }
868 return fetchPage(page + 1);
87 });
88 })(1);
89 };
90
91}).call(this);
92

/home/radek/dev/burnchart/src/modules/github/request.coffee

73%
124
91
33
LineHitsSource
12(function() {
22 var config, defaults, error, headers, isReady, isValid, 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 superagent.parse = {
15 'application/json': function(res) {
160 var e;
170 try {
180 return JSON.parse(res);
19 } catch (_error) {
200 e = _error;
210 return {};
22 }
23 }
24 };
25
262 defaults = {
27 'github': {
28 'host': 'api.github.com',
29 'protocol': 'https'
30 }
31 };
32
332 module.exports = {
34 repo: function(_arg, cb) {
350 var name, owner;
360 owner = _arg.owner, name = _arg.name;
370 if (!isValid({
38 owner: owner,
39 name: name
40 })) {
410 return cb('Request is malformed');
42 }
430 return ready(function() {
440 var data;
450 data = _.defaults({
46 'path': "/repos/" + owner + "/" + name,
47 'headers': headers(user.data.accessToken)
48 }, defaults.github);
490 return request(data, cb);
50 });
51 },
52 allMilestones: function(_arg, cb) {
531 var name, owner;
541 owner = _arg.owner, name = _arg.name;
551 if (!isValid({
56 owner: owner,
57 name: name
58 })) {
590 return cb('Request is malformed');
60 }
611 return ready(function() {
621 var data;
631 data = _.defaults({
64 'path': "/repos/" + owner + "/" + name + "/milestones",
65 'query': {
66 'state': 'open',
67 'sort': 'due_date',
68 'direction': 'asc'
69 },
70 'headers': headers(user.data.accessToken)
71 }, defaults.github);
721 return request(data, cb);
73 });
74 },
75 oneMilestone: function(_arg, cb) {
763 var milestone, name, owner;
773 owner = _arg.owner, name = _arg.name, milestone = _arg.milestone;
783 if (!isValid({
79 owner: owner,
80 name: name,
81 milestone: milestone
82 })) {
830 return cb('Request is malformed');
84 }
853 return ready(function() {
863 var data;
873 data = _.defaults({
88 'path': "/repos/" + owner + "/" + name + "/milestones/" + milestone,
89 'query': {
90 'state': 'open',
91 'sort': 'due_date',
92 'direction': 'asc'
93 },
94 'headers': headers(user.data.accessToken)
95 }, defaults.github);
963 return request(data, cb);
97 });
98 },
99 allIssues: function(_arg, query, cb) {
1002 var milestone, name, owner;
1012 owner = _arg.owner, name = _arg.name, milestone = _arg.milestone;
1022 if (!isValid({
103 owner: owner,
104 name: name,
105 milestone: milestone
106 })) {
1070 return cb('Request is malformed');
108 }
1092 return ready(function() {
1102 var data;
1112 data = _.defaults({
112 'path': "/repos/" + owner + "/" + name + "/issues",
113 'query': _.extend(query, {
114 milestone: milestone,
115 'per_page': '100'
116 }),
117 'headers': headers(user.data.accessToken)
118 }, defaults.github);
1192 return request(data, cb);
120 });
121 }
122 };
123
1242 request = function(_arg, cb) {
1256 var exited, headers, host, k, path, protocol, q, query, req, timeout, v;
1266 protocol = _arg.protocol, host = _arg.host, path = _arg.path, query = _arg.query, headers = _arg.headers;
1276 exited = false;
1286 q = query ? '?' + ((function() {
1296 var _results;
1306 _results = [];
1316 for (k in query) {
13216 v = query[k];
13316 _results.push("" + k + "=" + v);
134 }
1356 return _results;
136 })()).join('&') : '';
1376 req = superagent.get("" + protocol + "://" + host + path + q);
1386 for (k in headers) {
13912 v = headers[k];
14012 req.set(k, v);
141 }
1426 timeout = setTimeout(function() {
1431 exited = true;
1441 return cb('Request has timed out');
145 }, config.data.request.timeout);
1466 return req.end(function(err, data) {
1476 if (exited) {
1481 return;
149 }
1505 exited = true;
1515 clearTimeout(timeout);
1525 return response(err, data, cb);
153 });
154 };
155
1562 response = function(err, data, cb) {
1575 var _ref;
1585 if (err) {
1590 return cb(error(err));
160 }
1615 if (data.statusType !== 2) {
1622 if ((data != null ? (_ref = data.body) != null ? _ref.message : void 0 : void 0) != null) {
1631 return cb(data.body.message);
164 }
1651 return cb(data.error.message);
166 }
1673 return cb(null, data.body);
168 };
169
1702 headers = function(token) {
1716 var h;
1726 h = {
173 'Content-Type': 'application/json',
174 'Accept': 'application/vnd.github.v3'
175 };
1766 if (token != null) {
1770 h.Authorization = "token " + token;
178 }
1796 return h;
180 };
181
1822 isValid = function(obj) {
1836 var key, rules, val;
1846 rules = {
185 'owner': function(val) {
1866 return val != null;
187 },
188 'name': function(val) {
1896 return val != null;
190 },
191 'milestone': function(val) {
1925 return _.isInt(val);
193 }
194 };
1956 for (key in obj) {
19617 val = obj[key];
19717 if (key in rules && !rules[key](val)) {
1980 return false;
199 }
200 }
2016 return true;
202 };
203
2042 isReady = user.data.ready;
205
2062 stack = [];
207
2082 ready = function(cb) {
2096 if (isReady) {
2106 return cb();
211 } else {
2120 return stack.push(cb);
213 }
214 };
215
2162 user.observe('ready', function(val) {
2174 var _results;
2184 isReady = val;
2194 if (val) {
2202 _results = [];
2212 while (stack.length) {
2220 _results.push(stack.shift()());
223 }
2242 return _results;
225 }
226 });
227
2282 error = function(err) {
2290 var message;
2300 switch (false) {
231 case !_.isString(err):
2320 message = err;
2330 break;
234 case !_.isArray(err):
2350 message = err[1];
2360 break;
237 case !(_.isObject(err) && _.isString(err.message)):
2380 message = err.message;
239 }
2400 if (!message) {
2410 try {
2420 message = JSON.stringify(err);
243 } catch (_error) {
2440 message = err.toString();
245 }
246 }
2470 return message;
248 };
249
250}).call(this);
251

/home/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

/home/radek/dev/burnchart/src/modules/stats.coffee

97%
34
33
1
LineHitsSource
11(function() {
21 var moment, progress;
3
41 moment = require('moment');
5
61 progress = function(a, b) {
76 if (a + b === 0) {
80 return 0;
9 } else {
106 return 100 * (a / (b + a));
11 }
12 };
13
141 module.exports = function(milestone) {
1519 var a, b, c, days, isDone, isEmpty, isOnTime, isOverdue, points, time;
1619 if (milestone.stats != null) {
1714 return milestone.stats;
18 }
195 isDone = false;
205 isOnTime = true;
215 isOverdue = false;
225 isEmpty = true;
235 points = 0;
245 a = milestone.issues.closed.size;
255 b = milestone.issues.open.size;
265 if (a + b > 0) {
273 isEmpty = false;
283 points = progress(a, b);
293 if (points === 100) {
301 isDone = true;
31 }
32 }
335 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 }
443 a = +new Date(milestone.created_at);
453 b = +(new Date);
463 c = +new Date(milestone.due_on);
473 if (b > c) {
481 isOverdue = true;
49 }
503 time = progress(b - a, c - b);
513 days = (moment(b).diff(moment(a), 'days')) / 100;
523 isOnTime = points > time;
533 return {
54 isDone: isDone,
55 days: days,
56 isOnTime: isOnTime,
57 isOverdue: isOverdue,
58 'progress': {
59 points: points,
60 time: time
61 }
62 };
63 };
64
65}).call(this);
66

/home/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) {
215 return !isNaN(val) && parseInt(Number(val)) === val && !isNaN(parseInt(val, 10));
22 }
23 });
24
25}).call(this);
26

/home/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

/home/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/docs/TODO.md b/docs/TODO.md index 625d7e5..48ae735 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -10,11 +10,11 @@ ##Next Release -- [ ] make coverage work - [ ] http://burnchart.io#rails I would expect it to list all the projects for that owner so I can select one of them (Ryan); we could show a list of available project names with their: `description`, `private` flag and `has_issues` making the project greyed out if no issues found ##Backlog +- [ ] coverage using `blanket` does not work in tests that `proxyquire` - [ ] highlight today in the chart better - [ ] one click to go from a project or milestone view to github - [ ] be able to specify milestone by name (will nicely show in title) diff --git a/src/modules/github/issues.coffee b/src/modules/github/issues.coffee index fe96681..7431ef1 100644 --- a/src/modules/github/issues.coffee +++ b/src/modules/github/issues.coffee @@ -44,6 +44,9 @@ calcSize = (list) -> # Issues without size (no matching labels) are not saved. !!issue.size + else + throw 500 + # Sync return. { list, size } diff --git a/src/modules/stats.coffee b/src/modules/stats.coffee index 94843d8..4e082bb 100644 --- a/src/modules/stats.coffee +++ b/src/modules/stats.coffee @@ -7,6 +7,9 @@ progress = (a, b) -> # Calculate the stats for a milestone. # Is it on time? What is the progress? module.exports = (milestone) -> + # Makes testing easier... + return milestone.stats if milestone.stats? + isDone = no ; isOnTime = yes ; isOverdue = no ; isEmpty = yes; points = 0 # Progress in points. diff --git a/test/issues.coffee b/test/issues.coffee index 1d600e9..9adf328 100644 --- a/test/issues.coffee +++ b/test/issues.coffee @@ -1,13 +1,8 @@ -proxy = do require('proxyquire').noCallThru assert = require 'assert' -path = require 'path' -request = {} - -issues = proxy path.resolve(__dirname, '../src/modules/github/issues.coffee'), - './request.coffee': request - -config = require '../src/models/config.coffee' +request = require '../src/modules/github/request.coffee' +issues = require '../src/modules/github/issues.coffee' +config = require '../src/models/config.coffee' repo = { 'owner': 'radekstepan', 'name': 'burnchart', 'milestone': 1 } diff --git a/test/projects.coffee b/test/projects.coffee index 0927117..9907d99 100644 --- a/test/projects.coffee +++ b/test/projects.coffee @@ -1,11 +1,6 @@ -proxy = do require('proxyquire').noCallThru assert = require 'assert' -path = require 'path' -_ = require 'lodash' -projects = proxy path.resolve(__dirname, '../src/models/projects.coffee'), - # Just return the stats that are on the milestone already. - '../modules/stats.coffee': ({ stats }) -> stats +projects = require '../src/models/projects.coffee' module.exports = @@ -17,7 +12,7 @@ module.exports = do projects.clear project = { 'owner': 'radekstepan', 'name': 'burnchart' } - milestone = 'title': '1.0.0' + milestone = 'title': '1.0.0', 'stats': {} projects.push 'list', project projects.addMilestone project, milestone @@ -108,8 +103,8 @@ module.exports = projects.set 'sortBy', 'name' project = { 'owner': 'radekstepan', 'name': 'burnchart' } - milestone1 = 'title': 'B' - milestone2 = 'title': 'A' + milestone1 = 'title': 'B', 'stats': {} + milestone2 = 'title': 'A', 'stats': {} projects.push 'list', project projects.addMilestone project, milestone1 @@ -125,9 +120,9 @@ module.exports = projects.set 'sortBy', 'name' project = { 'owner': 'radekstepan', 'name': 'burnchart' } - milestone1 = 'title': '1.2.5' - milestone2 = 'title': '1.1.x' - milestone3 = 'title': '1.1.7' + milestone1 = 'title': '1.2.5', 'stats': {} + milestone2 = 'title': '1.1.x', 'stats': {} + milestone3 = 'title': '1.1.7', 'stats': {} projects.push 'list', project projects.addMilestone project, milestone1 diff --git a/test/ractive.coffee b/test/ractive.coffee new file mode 100644 index 0000000..181b0b6 --- /dev/null +++ b/test/ractive.coffee @@ -0,0 +1,31 @@ +assert = require 'assert' + +RactiveEventful = require '../src/utils/ractive/eventful.coffee' + +module.exports = -> + # This represents a way for mediator subscriptions to get cancelled. + 'mediator subscriptions get cancelled': (done) -> + # Need to be able to deal with custom context. + ctx = 'called': 0 + + view = new RactiveEventful + + onconstruct: -> + # Track how many times we get called. + @subscribe '!event', -> + @called += 1 + , ctx + + onteardown: -> + # Need to deal with multiple teardown handlers + do this._super + + do view.render() + + view.publish '!event' + assert.equal ctx.called, 1 + do view.teardown + view.publish '!event' + assert.equal ctx.called, 1 + + do done \ No newline at end of file