yaml config
This commit is contained in:
parent
23d0cb4001
commit
afec4bd24f
|
@ -0,0 +1,33 @@
|
||||||
|
Server for connect to GitHub Issues API and displaying a burndown chart for a current milestone.
|
||||||
|
|
||||||
|
## Requirements:
|
||||||
|
|
||||||
|
You can install all the following dependencies by running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -d
|
||||||
|
```
|
||||||
|
|
||||||
|
- [CoffeeScript](http://coffeescript.org/)
|
||||||
|
- [express](http://expressjs.com/)
|
||||||
|
- [eco](https://github.com/sstephenson/eco)
|
||||||
|
- [js-yaml](https://github.com/visionmedia/js-yaml)
|
||||||
|
|
||||||
|
## Configure:
|
||||||
|
|
||||||
|
The app is configured by pointing to a public GitHub user/project. Do so in `config.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
github_user: ̈́'intermine'
|
||||||
|
github_project: 'InterMine'
|
||||||
|
project_name: 'Core InterMine Project'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use:
|
||||||
|
|
||||||
|
1. Start a node server using `.webserver.sh`.
|
||||||
|
2. Visit [http://0.0.0.0:3000/](http://0.0.0.0:3000/)
|
||||||
|
|
||||||
|
## Example:
|
||||||
|
|
||||||
|
![image](https://raw.github.com/radekstepan/github-burndown-chart/master/example.png)
|
84
app.coffee
84
app.coffee
|
@ -2,21 +2,41 @@ express = require 'express'
|
||||||
eco = require 'eco'
|
eco = require 'eco'
|
||||||
https = require 'https'
|
https = require 'https'
|
||||||
fs = require "fs"
|
fs = require "fs"
|
||||||
|
yaml = require "js-yaml"
|
||||||
|
|
||||||
# Make HTTPS GET to GitHub API v3.
|
# Helper object for GitHub Issues.
|
||||||
apiGet = (path, type, callback) ->
|
Issues =
|
||||||
options =
|
|
||||||
host: "api.github.com"
|
|
||||||
method: "GET"
|
|
||||||
path: path
|
|
||||||
|
|
||||||
https.request(options, (response) ->
|
# Make HTTPS GET to GitHub API v3.
|
||||||
if response.statusCode is 200
|
get: (path, type, callback) ->
|
||||||
json = ""
|
options =
|
||||||
response.on "data", (chunk) -> json += chunk
|
host: "api.github.com"
|
||||||
|
method: "GET"
|
||||||
response.on "end", -> callback JSON.parse(json), type
|
path: path
|
||||||
).end()
|
|
||||||
|
https.request(options, (response) ->
|
||||||
|
if response.statusCode is 200
|
||||||
|
json = ""
|
||||||
|
response.on "data", (chunk) -> json += chunk
|
||||||
|
|
||||||
|
response.on "end", -> callback JSON.parse(json), type
|
||||||
|
).end()
|
||||||
|
|
||||||
|
# URLs to API.
|
||||||
|
getOpenIssues: (callback) -> Issues.get "/repos/#{Issues.config.github_user}/#{Issues.config.github_project}/issues?state=open", 'issues', callback
|
||||||
|
getClosedIssues: (callback) -> Issues.get "/repos/#{Issues.config.github_user}/#{Issues.config.github_project}/issues?state=closed", 'issues', callback
|
||||||
|
getMilestones: (callback) -> Issues.get "/repos/#{Issues.config.github_user}/#{Issues.config.github_project}/milestones", 'milestones', callback
|
||||||
|
|
||||||
|
# Convert GitHub ISO to JS ISO date and then time
|
||||||
|
dateToTime: (date) -> new Date(date[0...date.length - 1] + '.000' + date.charAt date.length-1).getTime()
|
||||||
|
|
||||||
|
# Format issues for display in a listing.
|
||||||
|
format: (issue) ->
|
||||||
|
# Format the timestamps.
|
||||||
|
if issue.created_at? then issue.created_at = new Date(Issues.dateToTime(issue.created_at)).toUTCString()
|
||||||
|
if issue.updated_at? then issue.updated_at = new Date(Issues.dateToTime(issue.updated_at)).toUTCString()
|
||||||
|
|
||||||
|
issue
|
||||||
|
|
||||||
# Express.
|
# Express.
|
||||||
app = express.createServer()
|
app = express.createServer()
|
||||||
|
@ -55,10 +75,7 @@ app.get '/burndown', (req, res) ->
|
||||||
when 'milestones' then store.milestones = store.milestones.concat data
|
when 'milestones' then store.milestones = store.milestones.concat data
|
||||||
|
|
||||||
# Are we done?
|
# Are we done?
|
||||||
if resources is 0
|
if resources is 0
|
||||||
# Convert GitHub ISO to JS ISO date and then time
|
|
||||||
dateToTime = (date) -> new Date(date[0...date.length - 1] + '.000' + date.charAt date.length-1).getTime()
|
|
||||||
|
|
||||||
# Store the current milestone and its size.
|
# Store the current milestone and its size.
|
||||||
current = { 'milestone': {}, 'diff': +Infinity, 'size': 0 }
|
current = { 'milestone': {}, 'diff': +Infinity, 'size': 0 }
|
||||||
|
|
||||||
|
@ -67,7 +84,7 @@ app.get '/burndown', (req, res) ->
|
||||||
for milestone in store.milestones
|
for milestone in store.milestones
|
||||||
due = milestone['due_on']
|
due = milestone['due_on']
|
||||||
# JS expects more accuracy.
|
# JS expects more accuracy.
|
||||||
due = dateToTime due
|
due = Issues.dateToTime due
|
||||||
# Is this the 'current' one?
|
# Is this the 'current' one?
|
||||||
diff = due - now
|
diff = due - now
|
||||||
if diff > 0 and diff < current.diff
|
if diff > 0 and diff < current.diff
|
||||||
|
@ -75,7 +92,7 @@ app.get '/burndown', (req, res) ->
|
||||||
|
|
||||||
# Create n dict with all dates in the milestone span.
|
# Create n dict with all dates in the milestone span.
|
||||||
days = {} ; totalDays = 0
|
days = {} ; totalDays = 0
|
||||||
day = dateToTime current.milestone.created_at # TODO: shift this to the start of the day and deal with time shifts.
|
day = Issues.dateToTime current.milestone.created_at # TODO: shift this to the start of the day and deal with time shifts.
|
||||||
while day < current.due
|
while day < current.due
|
||||||
# Save the day.
|
# Save the day.
|
||||||
days[day] = { 'issue': {}, 'actual': 0, 'ideal': 0 }
|
days[day] = { 'issue': {}, 'actual': 0, 'ideal': 0 }
|
||||||
|
@ -100,7 +117,7 @@ app.get '/burndown', (req, res) ->
|
||||||
current.size += issue.size
|
current.size += issue.size
|
||||||
# Is it closed?
|
# Is it closed?
|
||||||
if issue.closed_at?
|
if issue.closed_at?
|
||||||
closed = dateToTime issue.closed_at
|
closed = Issues.dateToTime issue.closed_at
|
||||||
# Find when was it closed (will be made faster)
|
# Find when was it closed (will be made faster)
|
||||||
day = do () ->
|
day = do () ->
|
||||||
for day, x of days
|
for day, x of days
|
||||||
|
@ -124,22 +141,31 @@ app.get '/burndown', (req, res) ->
|
||||||
|
|
||||||
# Finally send to client.
|
# Finally send to client.
|
||||||
res.render 'burndown',
|
res.render 'burndown',
|
||||||
'days': days
|
'days': days
|
||||||
|
'project': Issues.config.project_name
|
||||||
, (html) -> res.send html, 'Content-Type': 'text/html', 200
|
, (html) -> res.send html, 'Content-Type': 'text/html', 200
|
||||||
|
|
||||||
|
|
||||||
# Get Milestones, Opened and Closed Tickets.
|
# Get Milestones, Opened and Closed Tickets.
|
||||||
apiGet "/repos/intermine/InterMine/milestones", 'milestones', done
|
Issues.getMilestones done
|
||||||
apiGet "/repos/intermine/InterMine/issues?state=open", 'issues', done
|
Issues.getOpenIssues done
|
||||||
apiGet "/repos/intermine/InterMine/issues?state=closed", 'issues', done
|
Issues.getClosedIssues done
|
||||||
|
|
||||||
# Show open issues.
|
# Show open issues.
|
||||||
app.get '/issues', (req, res) ->
|
app.get '/issues', (req, res) ->
|
||||||
apiGet "/repos/intermine/InterMine/issues?state=open", 'issues', (issues) ->
|
Issues.getOpenIssues (issues) ->
|
||||||
# Vanilla render.
|
|
||||||
|
# Replace the dates in issues with nice dates.
|
||||||
|
issues = ( Issues.format(issue) for issue in issues )
|
||||||
|
|
||||||
res.render 'issues',
|
res.render 'issues',
|
||||||
'issues': issues
|
'issues': issues
|
||||||
|
'project': Issues.config.project_name
|
||||||
, (html) -> res.send html, 'Content-Type': 'text/html', 200
|
, (html) -> res.send html, 'Content-Type': 'text/html', 200
|
||||||
|
|
||||||
app.listen 3000
|
# Fetch config and start server.
|
||||||
console.log "Express server listening to port 3000"
|
fs.readFile "config.yml", "utf8", (err, data) ->
|
||||||
|
Issues.config = yaml.load data
|
||||||
|
|
||||||
|
app.listen 3000
|
||||||
|
console.log "Express server listening to port 3000"
|
|
@ -0,0 +1,3 @@
|
||||||
|
github_user: 'intermine'
|
||||||
|
github_project: 'InterMine'
|
||||||
|
project_name: 'Core InterMine Project'
|
Binary file not shown.
After Width: | Height: | Size: 131 KiB |
|
@ -3,7 +3,8 @@
|
||||||
, "version": "0.0.0"
|
, "version": "0.0.0"
|
||||||
, "private": true
|
, "private": true
|
||||||
, "dependencies": {
|
, "dependencies": {
|
||||||
"express": ">= 3.0",
|
"express": ">= 3.0",
|
||||||
"eco": "latest"
|
"eco": "latest",
|
||||||
|
"js-yaml": "latest"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -22,7 +22,7 @@
|
||||||
<li><a><i class="icon-white icon-fire"></i> Burndown App</a></li>
|
<li><a><i class="icon-white icon-fire"></i> Burndown App</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav pull-right">
|
<ul class="nav pull-right">
|
||||||
<li><a><i class="icon-white icon-book"></i> Core InterMine Project</a></li>
|
<li><a><i class="icon-white icon-book"></i> <%= @project %></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<li><a><i class="icon-white icon-fire"></i> Burndown App</a></li>
|
<li><a><i class="icon-white icon-fire"></i> Burndown App</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav pull-right">
|
<ul class="nav pull-right">
|
||||||
<li><a><i class="icon-white icon-book"></i> Core InterMine Project</a></li>
|
<li><a><i class="icon-white icon-book"></i> <%= @project %></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue