diff --git a/src/app.coffee b/src/app.coffee index 64f5c37..577e2d8 100644 --- a/src/app.coffee +++ b/src/app.coffee @@ -3,52 +3,56 @@ async = require 'async' Rickshaw = require 'rickshaw' -graph = require './graph' +# Modules. +milestones = require './milestones' +issues = require './issues' +graph = require './graph' +reg = require './regex' -templates = - 'body': require './body' - 'label': require './label' +# Eco templates as functions. +templates = {} +( templates[t] = require("./#{t}") for t in [ 'body', 'label' ] ) + +user = 'radekstepan' +repo = 'disposable' module.exports = -> - a = { number: 2, closed_at: '2013-05-09T09:04:53Z', size: 6 } - b = { number: 1, closed_at: '2013-05-20T10:04:53Z', size: 4 } - c = { number: 3, closed_at: '2013-06-15T09:04:53Z', size: 2 } + milestones.get_current { user, repo }, (err, warn, m) -> + issues.get_all { user, repo, milestone: m.number }, (err, [ open, closed ]) -> + issues.filter closed, reg.size_label, (err, warn, closed) -> + async.parallel [ + _.partial(graph.actual, closed, m.created_at, 10) + _.partial(graph.ideal, m.created_at, m.due_on, 10) + ], (err, [ actual, ideal ]) -> + document.querySelector('body').innerHTML = templates.body({}) - async.parallel [ - _.partial(graph.actual, [ a, b, c ], 20) - _.partial(graph.ideal, '2013-05-09T09:04:53Z', '2013-08-29T09:04:53Z', 20) - ], (err, [ actual, ideal ]) -> - throw err if err + graph = new Rickshaw.Graph + 'element': document.querySelector('#graph') + 'renderer': 'line' + 'series': [ + { 'data': actual, 'color': '#73C03A', 'name': 'actual' } + { 'data': ideal, 'color': 'rgba(0,0,0,0.2)', 'name': 'ideal' } + ] - document.querySelector('body').innerHTML = templates.body({}) + hoverDetail = new Rickshaw.Graph.HoverDetail + 'graph': graph + 'xFormatter': (timestamp) -> + new Date(timestamp * 1e3).toUTCString().substring(0, 11) + + 'formatter': (series, timestamp, points) -> + templates.label { 'class': series.name, points } + + xAxis = new Rickshaw.Graph.Axis.Time 'graph': graph - graph = new Rickshaw.Graph - 'element': document.querySelector('#graph') - 'renderer': 'line' - 'series': [ - { 'data': actual, 'color': '#73C03A', 'name': 'actual' } - { 'data': ideal, 'color': 'rgba(0,0,0,0.2)', 'name': 'ideal' } - ] + yAxis = new Rickshaw.Graph.Axis.Y + 'graph': graph + 'orientation': 'left' + 'tickFormat': Rickshaw.Fixtures.Number.formatKMBT - hoverDetail = new Rickshaw.Graph.HoverDetail - 'graph': graph - 'xFormatter': (timestamp) -> - new Date(timestamp * 1e3).toUTCString().substring(0, 11) - - 'formatter': (series, timestamp, points) -> - templates.label { 'class': series.name, points } - - xAxis = new Rickshaw.Graph.Axis.Time 'graph': graph + annotator = new Rickshaw.Graph.Annotate + 'graph': graph + 'element': document.querySelector('#timeline') + + annotator.add +new Date / 1e3, 'Now' - yAxis = new Rickshaw.Graph.Axis.Y - 'graph': graph - 'orientation': 'left' - 'tickFormat': Rickshaw.Fixtures.Number.formatKMBT - - annotator = new Rickshaw.Graph.Annotate - 'graph': graph - 'element': document.querySelector('#timeline') - - annotator.add +new Date / 1e3, 'Now' - - graph.render() \ No newline at end of file + graph.render() \ No newline at end of file diff --git a/src/component.json b/src/component.json index 1f15c1b..1d1d555 100644 --- a/src/component.json +++ b/src/component.json @@ -5,12 +5,12 @@ "dependencies": { "bestiejs/lodash": "*", "caolan/async": "*", - "cristiandouce/rickshaw": "*" + "cristiandouce/rickshaw": "*", + "visionmedia/superagent": "*" }, "scripts": [ "app.coffee", "modules/graph.coffee", - "modules/dates.coffee", "modules/issues.coffee", "modules/milestones.coffee", "modules/regex.coffee", diff --git a/src/modules/dates.coffee b/src/modules/dates.coffee deleted file mode 100644 index bb8382d..0000000 --- a/src/modules/dates.coffee +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env coffee -{ _ } = require 'lodash' -reg = require './regex' - -module.exports = - # Create a range of days between two dates. - 'range': ({ a, b }, cb) -> - # Swap? - [ b, a ] = [ a, b ] if b < a - - # When do we start & end? - [ year, month, day ] = _.map(a.match(reg.datetime)[1].split('-'), (d) -> parseInt(d) ) - b = b.match(reg.datetime)[1] - - days = [] - do add = (i = 0) -> - # Use 12 hours to handle daylight saving. - days.push c = new Date(year, month - 1, day + i, 12).toJSON().match(reg.datetime)[1] - add(i + 1) unless c is b - - cb null, days \ No newline at end of file diff --git a/src/modules/graph.coffee b/src/modules/graph.coffee index a3ef01b..708786d 100644 --- a/src/modules/graph.coffee +++ b/src/modules/graph.coffee @@ -1,28 +1,45 @@ #!/usr/bin/env coffee { _ } = require 'lodash' -dates = require './dates' +reg = require './regex' module.exports = # Map closed issues ready to be visualized by Rickshaw. # Assumes collection has been `filter`ed and is ordered. - 'actual': (collection, total, cb) -> - cb null, _.map collection, ({ closed_at, size }) -> + 'actual': (collection, created_at, total, cb) -> + head = [ { x: +new Date(created_at) / 1e3, y: total } ] + rest = _.map collection, ({ closed_at, size }) -> { x: +new Date(closed_at) / 1e3, y: total -= size } + cb null, head.concat rest # Map ideal velocity for each day ready to be visualized by Rickshaw. 'ideal': (a, b, total, cb) -> # Swap? [ b, a ] = [ a, b ] if b < a - # Generate the days in between. - dates.range { a, b }, (err, data) -> - return cb err if err + # When do we start & end? + [ year, month, day ] = _.map(a.match(reg.datetime)[1].split('-'), (d) -> parseInt(d) ) + b = b.match(reg.datetime)[1] - # Daily velocity needed. - daily = total / data.length - # Map days to data points. - data = _.map data, (day) -> - { x: +new Date(day) / 1e3, y: total -= daily } + # The head/tail are quite specific. + head = { x: +new Date(a) / 1e3, y: total } + tail = { x: b = +new Date(b) / 1e3, y: 0 } - cb null, data \ No newline at end of file + # The fillers... + days = [] + do add = (i = 1) -> + # Lunchtime to "handle" daylight saving. + c = +new Date year, month - 1, day + i, 12 + # Add the time point. + days.push { x: c / 1e3 } + # Moar? + add(i + 1) if c < b + + # Daily velocity needed. + daily = total / (days.length + 1) + # Map points to days. + days = _.map days, (day) -> + day.y = total -= daily + day + + cb null, [ head ].concat(days).concat([ tail ]) \ No newline at end of file diff --git a/src/modules/issues.coffee b/src/modules/issues.coffee index 017c116..16c39c4 100644 --- a/src/modules/issues.coffee +++ b/src/modules/issues.coffee @@ -7,16 +7,20 @@ reg = require './regex' module.exports = # Used on an initial fetch of issues for a repo. - 'get_all': (opts, cb) -> - done = no - - # For each status... - one_status = (status, cb) -> + 'get_all': ({ user, repo, milestone }, cb) -> + # For each state... + one_status = (state, cb) -> # Concat them here. results = [] # One pageful fetch (next pages in series). do fetch_page = (page = 1) -> - req.all_issues { status: status, page: page }, (err, data) -> + req.all_issues { + user + repo + milestone + state: state + page: page + }, (err, data) -> # Request errors. return cb err if err # GitHub errors. diff --git a/src/modules/milestones.coffee b/src/modules/milestones.coffee index 83b0f69..cbd9ef7 100644 --- a/src/modules/milestones.coffee +++ b/src/modules/milestones.coffee @@ -11,9 +11,8 @@ module.exports = return cb data.message if data.message # Empty warning. return cb null, 'No open milestones for repo' unless data.length - # Find the one due on soonest (string comparison). - max = { 'due_on': 'A' } - ( max = ms for ms in data when ms.due_on < max.due_on ) + # The first milestone should be ending soonest. + m = data[0] # Empty milestone? - return cb null, 'No issues for milestone' if max.open_issues + max.closed_issues is 0 - cb null, null, max \ No newline at end of file + return cb null, 'No issues for milestone' if m.open_issues + m.closed_issues is 0 + cb null, null, m \ No newline at end of file diff --git a/src/modules/request.coffee b/src/modules/request.coffee index 1b31244..916f314 100644 --- a/src/modules/request.coffee +++ b/src/modules/request.coffee @@ -1,7 +1,27 @@ #!/usr/bin/env coffee +{ _ } = require 'lodash' + +protocol = 'https' +domain = 'api.github.com' +token = '' + module.exports = - 'all_milestones': (opts, cb) -> - cb 'Not implemented' - 'all_issues': (opts, cb) -> - # { direction: 'asc', per_page: 100 } - cb 'Not implemented' \ No newline at end of file + 'all_milestones': ({ user, repo }, cb) -> + opts = { state: 'open', sort: 'due_date', direction: 'asc' } + request { user, repo, opts, path: 'milestones' }, cb + + 'all_issues': ({ user, repo }, cb) -> + opts = _.extend {}, arguments[0], { per_page: '100', direction: 'asc' } + request { user, repo, opts, path: 'issues' }, cb + +# Make a request using SuperAgent. +request = ({ user, repo, path, opts }, cb) -> + opts = ( "#{k}=#{v}" for k, v of opts when k not in [ 'user', 'repo' ] ).join('&') + + (require 'superagent') + .get("#{protocol}://#{domain}/repos/#{user}/#{repo}/#{path}?#{opts}") + .set('Content-Type', 'application/json') + .set('Accept', 'application/vnd.github.raw') + .set('Authorization', "token #{token}") + .end (err, data) -> + cb err, data?.body \ No newline at end of file diff --git a/test/dates.coffee b/test/dates.coffee deleted file mode 100644 index ac4cb4a..0000000 --- a/test/dates.coffee +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env coffee -assert = require 'assert' -path = require 'path' -{ _ } = require 'lodash' -moment = require 'moment' - -dates = require path.resolve __dirname, '../src/modules/dates.coffee' - -tests = - 'range between two dates': - [ '2013-01-01T00:00:00Z', '2013-01-02T00:00:00Z' ] - 'range regardless of the order': - [ '2013-01-02T00:00:00Z', '2013-01-01T00:00:00Z' ] - 'range across a year': - [ '2012-12-12T00:00:00Z', '2013-01-05T00:00:00Z' ] - 'range on the same day': - [ '2012-12-12T00:00:00Z', '2013-12-12T00:00:00Z' ] - 'daylight saving': - [ '2013-05-09T09:04:53Z', '2013-05-12T09:04:53Z' ] - -for key, value of tests then do (key, value) -> - exports[key] = (done) -> - [ a, b ] = value - dates.range { a, b }, (err, data) -> - assert.ifError err - assert.equal data.length, Math.abs(moment(a).diff(moment(b), 'days')) + 1 - _.each data, (date) -> assert moment(date).isValid() - done.call null \ No newline at end of file diff --git a/test/graph.coffee b/test/graph.coffee index d373f1a..8f3cc2a 100644 --- a/test/graph.coffee +++ b/test/graph.coffee @@ -10,9 +10,10 @@ module.exports = b = { number: 1, closed_at: '2013-05-09T10:04:53Z', size: 4 } c = { number: 3, closed_at: '2013-05-12T09:04:53Z', size: 2 } - graph.actual [ a, b, c ], 20, (err, data) -> + graph.actual [ a, b, c ], '2013-05-08T09:04:53Z', 20, (err, data) -> assert.ifError err assert.deepEqual data, [ + { x: 1368003893, y: 20 } { x: 1368090293, y: 14 } { x: 1368093893, y: 10 } { x: 1368349493, y: 8 } diff --git a/test/milestones.coffee b/test/milestones.coffee index a487991..a403272 100644 --- a/test/milestones.coffee +++ b/test/milestones.coffee @@ -24,19 +24,20 @@ module.exports = assert.equal milestone.number, 1 done.call null + # We always take from head because of request params. 'get current from > 1': (done) -> req.all_milestones = (opts, cb) -> cb null, [ - { - 'number': 1 - 'created_at': '2013-01-01T00:00:00Z' - 'due_on': '2013-02-01T00:00:00Z' - } { 'number': 2 'created_at': '2013-01-01T00:00:00Z' 'due_on': '2013-01-15T00:00:00Z' } + { + 'number': 1 + 'created_at': '2013-01-01T00:00:00Z' + 'due_on': '2013-02-01T00:00:00Z' + } { 'number': 3 'created_at': '2013-01-01T00:00:00Z'