doc code; reorg repo

This commit is contained in:
Radek Stepan 2013-09-26 22:21:14 +01:00
parent 256eb5388b
commit 5841eb2c46
11 changed files with 146 additions and 147 deletions

View File

@ -2,27 +2,28 @@
async = require 'async'
{ _ } = require 'lodash'
config = require './modules/config'
render = require './modules/render'
{ Repo } = require './modules/repo'
config = require './modules/config'
regex = require './modules/regex'
render = require './modules/render'
repo = require './modules/repo'
# Check for a route.
route = ->
if match = window.location.hash.match /^#!\/(.+)\/(.+)$/
repo = match[1..3].join('/')
# Do we have a location match?
if match = window.location.hash.match regex.location
# Get the user/repo pair then.
path = match[1..3].join('/')
# We are loading.
render 'body', 'loading', { repo }
# Say we are loading this repo then.
render 'body', 'loading', { path }
# Get config/cache.
return async.waterfall [ config
# Instantiate.
# Render this repo.
, (conf, cb) ->
cb null, new Repo _.extend { repo }, conf
# Render.
, (repo, cb) ->
repo.render cb
repo _.extend({ path }, conf), cb
], (err) ->
render 'body', 'error', { text: err.toString() } if err
render 'body', 'error', { 'text': err.toString() } if err
# Info notice for you.
render 'body', 'info'
@ -35,4 +36,4 @@ module.exports = ->
# And route now.
return do route
render 'body', 'error', { text: 'URL fragment identifier not supported' }
render 'body', 'error', { 'text': 'URL fragment identifier not supported' }

View File

@ -13,6 +13,7 @@
},
"scripts": [
"app.coffee",
"modules/config.coffee",
"modules/graph.coffee",
"modules/issues.coffee",
@ -21,6 +22,7 @@
"modules/request.coffee",
"modules/render.coffee",
"modules/repo.coffee",
"templates/error.eco",
"templates/graph.eco",
"templates/info.eco",

View File

@ -1,12 +1,24 @@
#!/usr/bin/env coffee
{ _ } = require 'lodash'
req = require './request'
request = require './request'
regex = require './regex'
# Have it?
config = null
# We are cold.
wait = no
# Callbacks go here.
queue = []
# Defaults.
defaults =
# You do know we work with GitHub right?
'host': 'api.github.com'
# Making NSA (err taxpayer) work for it.
'protocol': 'https'
# Get (& cache) configuration from the server.
module.exports = (cb) ->
# Have config?
return cb null, config if config
@ -14,10 +26,18 @@ module.exports = (cb) ->
queue.push cb
# Load it?
unless wait
# Everyone else wait now.
wait = yes
req.config (err, result) ->
# Make the request.
request.config (err, result) ->
# Save config?
config = result unless err
# Call back for each.
unless err
config = result
# Tack on defaults?
( config[k] ?= v for k, v of defaults )
# RegExpify the size label?
config.size_label = new RegExp(config.size_label) or regex.size_label
# Call back for each enqueued.
_.each queue, (cb) ->
cb err, result
cb err, config # is either null or provided by now

View File

@ -5,8 +5,9 @@ Tip = require 'tip'
reg = require './regex'
module.exports =
# Map closed issues.
module.exports =
# A graph of closed issues.
'actual': (collection, created_at, total, cb) ->
head = [ {
date: new Date(created_at)
@ -18,9 +19,11 @@ module.exports =
# Generate the actual closes.
rest = _.map collection, (issue) ->
{ size, closed_at } = issue
# Determine the range.
min = size if size < min
max = size if size > max
# Dropping points remaining.
_.extend {}, issue,
date: new Date(closed_at)
points: total -= size
@ -32,9 +35,9 @@ module.exports =
issue.radius = range issue.size
issue
cb null, head.concat rest
cb null, [].concat head, rest
# Map ideal velocity for each day.
# A graph of an ideal progression..
'ideal': (a, b, off_days, total, cb) ->
# Swap?
[ b, a ] = [ a, b ] if b < a
@ -74,11 +77,11 @@ module.exports =
cb null, days
# A trendline.
# http://classroom.synonym.com/calculate-trendline-2709.html
# Graph representing a trendling of actual issues.
'trendline': (actual, created_at, due_on) ->
start = +actual[0].date
# Values is a list of time from the start and points remaining.
values = _.map actual, ({ date, points }) ->
[ +date - start, points ]
@ -86,6 +89,7 @@ module.exports =
last = actual[actual.length - 1]
values.push [ + new Date() - start, last.points ]
# http://classroom.synonym.com/calculate-trendline-2709.html
b1 = 0 ; e = 0 ; c1 = 0
a = (l = values.length) * _.reduce(values, (sum, [ a, b ]) ->
b1 += a ; e += b
@ -93,19 +97,12 @@ module.exports =
sum + (a * b)
, 0)
b = b1 * e
c = l * c1
d = Math.pow(b1, 2)
slope = (a - b) / (c - d)
f = slope * b1
intercept = (e - f) / l
slope = (a - (b1 * e)) / ((l * c1) - (Math.pow(b1, 2)))
intercept = (e - (slope * b1)) / l
fn = (x) -> slope * x + intercept
a = +new Date(created_at) - start ; b = +new Date(due_on) - start
a = +new Date(created_at) - start
b = +new Date(due_on) - start
[
{
@ -117,7 +114,7 @@ module.exports =
}
]
# Render the D3 chart.
# The graph as a whole.
'render': ([ actual, ideal, trendline ], cb) ->
document.querySelector('#svg').innerHTML = ''
@ -128,10 +125,11 @@ module.exports =
width -= margin.left + margin.right
height -= margin.top + margin.bottom
# Scales and axes.
# Scales.
x = d3.time.scale().range([ 0, width ])
y = d3.scale.linear().range([ height, 0 ])
# Axes.
xAxis = d3.svg.axis().scale(x)
.orient("bottom")
# Show vertical lines...
@ -222,6 +220,7 @@ module.exports =
svg.selectAll("a.issue")
.data(actual[1...]) # skip the starting point
.enter()
# A wrapping link.
.append('svg:a')
.attr("xlink:href", ({ html_url }) -> html_url )

View File

@ -6,6 +6,7 @@ req = require './request'
reg = require './regex'
module.exports =
# Used on an initial fetch of issues for a repo.
'get_all': (repo, cb) ->
# For each state...
@ -60,23 +61,4 @@ module.exports =
cb null, warnings, filtered, total
catch err
return cb err, warnings
# Map a collection of closed issues into days and determine the velocity for the range of all days.
# Assumes collection has been `filter`ed and is ordered.
'into_days': (collection, regex, cb) ->
days = {}
for issue in collection
{ state, number, closed_at } = issue
number ?= '?'
return "Issue ##{number} does not have a `closed_at` parameter" unless closed_at
unless matches = closed_at.match reg.datetime
return "Issue ##{number} does not match the `closed_at` pattern"
# Explode the matches.
[ date, time ] = matches[1...]
# Save it.
days[date] ?= []
days[date].push issue
cb null, days
return cb err, warnings

View File

@ -1,10 +1,11 @@
#!/usr/bin/env coffee
req = require './request'
request = require './request'
module.exports =
# Used at initialization stage.
# Get current milestones for a repo..
'get_current': (repo, cb) ->
req.all_milestones repo, (err, data) ->
request.all_milestones repo, (err, data) ->
# Request errors.
return cb err if err
# GitHub errors.
@ -15,4 +16,5 @@ module.exports =
m = data[0]
# Empty milestone?
return cb null, 'No issues for milestone' if m.open_issues + m.closed_issues is 0
cb null, null, m

View File

@ -1,4 +1,8 @@
#!/usr/bin/env coffee
module.exports =
# How do we parse GitHub dates?
'datetime': /^(\d{4}-\d{2}-\d{2})T(.*)/
'size_label': /^size (\d+)$/
# How does a size label look like?
'size_label': /^size (\d+)$/
# How do we specify which user/repo we want?
'location': /^#!\/(.+)\/(.+)$/

View File

@ -1,6 +1,6 @@
#!/usr/bin/env coffee
# Render an eco template into a selector.
# Render an eco template into a selector (innerHTML).
module.exports = (selector, template, context = {}) ->
tml = require "../templates/#{template}"
document.querySelector(selector).innerHTML = tml context

View File

@ -5,91 +5,76 @@ async = require 'async'
milestones = require './milestones'
issues = require './issues'
graph = require './graph'
reg = require './regex'
req = require './request'
regex = require './regex'
render = require './render'
# Eco templates as functions.
tml = {}
( tml[t] = require("../templates/#{t}") for t in [ 'progress' ] )
# Setup a repo and render it.
module.exports = (opts, cb) ->
class exports.Repo
# Get the current milestone.
async.waterfall [ (cb) ->
milestones.get_current opts, (err, warn, milestone) ->
return cb err if err
return cb warn if warn
opts.milestone = milestone
cb null
constructor: (opts) ->
( @[k] = v for k, v of opts )
# Get all issues.
(cb) ->
issues.get_all opts, cb
# Filter them to labeled ones.
(all, cb) ->
async.map all, (array, cb) ->
issues.filter array, opts.size_label, (err, warn, filtered, total) ->
cb err, [ filtered, total ]
, (err, [ open, closed ]) ->
return cb err if err
# Empty?
return cb 'No matching issues found' if open[1] + closed[1] is 0
# Save the open/closed on us first.
opts.issues =
closed: { points: closed[1], data: closed[0] }
open: { points: open[1], data: open[0] }
cb null
# Create actual and ideal lines & render.
(cb) ->
progress = 100 * opts.issues.closed.points /
(total = opts.issues.open.points + opts.issues.closed.points)
# Defaults.
@host ?= 'api.github.com'
@protocol ?= 'https'
@size_label = new RegExp(@size_label) or reg.size_label
async.parallel [
_.partial(
graph.actual,
opts.issues.closed.data,
opts.milestone.created_at,
total
)
_.partial(
graph.ideal,
opts.milestone.created_at,
opts.milestone.due_on,
opts.off_days or [],
total
)
], (err, values) ->
# Render the body.
render 'body', 'graph', name: opts.repo
render: (cb) =>
self = @
# Render the progress.
render '#progress', 'progress', { progress }
async.waterfall [ (cb) ->
# Get the current milestone.
milestones.get_current self, (err, warn, milestone) ->
return cb err if err
return cb warn if warn
self.milestone = milestone
cb null
# Generate a trendline?
values.push(graph.trendline(
values[0],
opts.milestone.created_at,
opts.milestone.due_on
)) if values[0].length
# Get all issues.
(cb) ->
issues.get_all self, cb
# Filter them to labeled ones.
(all, cb) ->
async.map all, (array, cb) ->
issues.filter array, self.size_label, (err, warn, filtered, total) ->
cb err, [ filtered, total ]
, (err, [ open, closed ]) ->
return cb err if err
# Empty?
return cb 'No matching issues found' if open[1] + closed[1] is 0
# Save the open/closed on us first.
self.issues =
closed: { points: closed[1], data: closed[0] }
open: { points: open[1], data: open[0] }
cb null
# Create actual and ideal lines & render.
(cb) ->
progress = 100 * self.issues.closed.points /
(total = self.issues.open.points + self.issues.closed.points)
# Render the chart.
do doit = -> graph.render values, cb
async.parallel [
_.partial(
graph.actual,
self.issues.closed.data,
self.milestone.created_at,
total
)
_.partial(
graph.ideal,
self.milestone.created_at,
self.milestone.due_on,
self.off_days or [],
total
)
], (err, values) ->
# Render the body.
render 'body', 'graph', name: self.repo
# Watch window resize from now on?
window.onresize = doit if 'onresize' of window
# Render the progress.
render '#progress', 'progress', { progress }
# Generate a trendline?
values.push(graph.trendline(
values[0],
self.milestone.created_at,
self.milestone.due_on
)) if values[0].length
# Render the chart.
do doit = -> graph.render values, cb
# Watch window resize?
window.onresize = doit if 'onresize' of window
], cb
], cb

View File

@ -3,14 +3,15 @@ sa = require 'superagent'
{ _ } = require 'lodash'
module.exports =
# Get all milestones.
'all_milestones': (repo, cb) ->
query = { state: 'open', sort: 'due_date', direction: 'asc' }
query = { 'state': 'open', 'sort': 'due_date', 'direction': 'asc' }
request repo, query, 'milestones', cb
# Get all issues for a state.
'all_issues': (repo, query, cb) ->
_.extend query, { per_page: '100' }
_.extend query, { 'per_page': '100' }
request repo, query, 'issues', cb
# Get config from our host always.
@ -21,14 +22,17 @@ module.exports =
.end (err, data) ->
cb err, data?.body
# Make a request using SuperAgent.
request = ({ protocol, host, token, repo }, query, path, cb) ->
# Make a request using SuperAgent to GitHub.
request = ({ protocol, host, token, path }, query, noun, cb) ->
# Make the query params.
q = ( "#{k}=#{v}" for k, v of query ).join('&')
req = sa
.get("#{protocol}://#{host}/repos/#{repo}/#{path}?#{q}")
# The URI.
.get("#{protocol}://#{host}/repos/#{path}/#{noun}?#{q}")
# The content type.
.set('Content-Type', 'application/json')
# The media type.
.set('Accept', 'application/vnd.github.raw')
# Auth token?

View File

@ -1,4 +1,4 @@
<div class="box generic">
<h2>GitHub Burndown Chart</h2>
<p>Loading <a href="#!/<%- @repo %>">#!/<%- @repo %></a>.</p>
<p>Loading <a href="#!/<%- @path %>">#!/<%- @path %></a>.</p>
</div>