mirror of
https://github.com/status-im/burnchart.git
synced 2025-01-19 15:12:05 +00:00
doc code; reorg repo
This commit is contained in:
parent
256eb5388b
commit
5841eb2c46
@ -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' }
|
@ -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",
|
||||
|
@ -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
|
@ -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 )
|
||||
|
@ -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
|
@ -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
|
@ -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': /^#!\/(.+)\/(.+)$/
|
@ -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
|
@ -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
|
@ -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?
|
||||
|
@ -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>
|
Loading…
x
Reference in New Issue
Block a user