prep for chart to be used

This commit is contained in:
Radek Stepan 2014-09-17 20:00:01 -07:00
parent 648725c3dc
commit 21a0e82a85
14 changed files with 11015 additions and 101 deletions

View File

@ -38,6 +38,9 @@ module.exports = (grunt) ->
'vendor/localforage/dist/localforage.js'
'vendor/async/lib/async.js'
'vendor/moment/moment.js'
'vendor/d3/d3.js'
'vendor/d3-tip/index.js'
'vendor/marked/lib/marked.js'
# Our app.
'public/js/app.js'
]

View File

@ -6,16 +6,14 @@ GitHub Burndown Chart as a service. Public repos are free, for private access au
## Tasks
### MVP
### MVP - Community Plan
- [x] landing page allows you to immediately jump into action
- [ ] show chart for the current milestone, default to the first one returned and allow to choose a custom one
- [ ] sort projects based on their closest due dates
- [ ] show only repo name if all projects are under our name
- [x] show a list of projects and their milestones with progress & due date
- [ ] show burnchart for that project milestone
- [ ] show all issues as [one size](https://github.com/radekstepan/github-burndown-chart/issues/46)
- [x] use local storage to save information about us, but keep the API open for Firebase
- [x] use `localStorage` to save project names
### The 20%
### Extras
- [ ] Do not show login/logged-in state when we are still fetching that information from Firebase
- [ ] Handle 404 on routes; from catch all check if '/' or go 404 controller
@ -32,9 +30,6 @@ GitHub Burndown Chart as a service. Public repos are free, for private access au
- [ ] Since persistence is async, deal with the flicker (show laoding?) when we are still getting data
- [ ] On page load get all the latest data regardless of `time_ago`
- [ ] Show loading sign on top of [browser window](https://github.com/buunguyen/topbar) which is unobtrusive enough we can show it immediately.
### Extras
- [ ] rotate between percentage progress and points left
- [ ] be able to config options through UI that currently have to be hardcoded in config
- [ ] cache repos in `localStorage` for those that do not use GitHub login

View File

@ -13,6 +13,9 @@
"localforage": "~0.9.2",
"superagent": "~0.19.0",
"async": "~0.9.0",
"moment": "~2.8.3"
"moment": "~2.8.3",
"d3": "~3.4.11",
"d3-tip": "~0.6.5",
"marked": "~0.3.2"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -26,13 +26,17 @@
document.title = 'BurnChart: GitHub Burndown Chart as a Service';
Page = require("./views/pages/" + page);
return new Page({
el: el
el: el,
'data': {
'route': req.params
}
});
};
router = {
'': _.partial(route, 'index'),
'project/add': _.partial(route, 'addProject'),
'chart/:owner/:name/:milestone': _.partial(route, 'showChart'),
'reset': function() {
mediator.fire('!projects/clear');
return window.location.hash = '#';
@ -96,36 +100,31 @@
'list': []
},
init: function() {
var getMilestones,
_this = this;
getMilestones = function() {};
var _this = this;
localforage.getItem('projects', function(projects) {
if (projects == null) {
projects = [];
}
return _this.set('list', projects);
return async.each(projects, function(project, cb) {
return mediator.fire('!projects/add', project);
}, function(err) {
if (err) {
throw err;
}
});
});
this.observe('list', function(projects) {
return localforage.setItem('projects', projects);
});
mediator.on('!projects/add', function(repo, done) {
return request.allMilestones(repo, function(err, res) {
var active, milestones;
var milestones;
if (err) {
throw err;
return done(err);
}
milestones = _.pluckMany(res, config.fields.milestone);
active = _.find(milestones, function(m) {
return 0 < m.open_issues + m.closed_issues;
});
if (active != null) {
active.active = true;
}
_this.push('list', _.merge(repo, {
'milestones': {
'list': milestones,
'checked_at': date.now()
}
milestones: milestones
}));
return done();
});
@ -390,7 +389,7 @@
// layout.mustache
root.require.register('burnchart/src/templates/layout.js', function(exports, require, module) {
module.exports = ["<Header/>","","<div id=\"page\">"," <!-- content loaded from a router -->","</div>","","<div id=\"footer\">"," <div class=\"wrap\">"," &copy; 2012-2014 Radek Stepan"," </div>","</div>"].join("\n");
module.exports = ["<Header/>","","<div id=\"page\">"," <!-- content loaded from a router -->","</div>","","<div id=\"footer\">"," <div class=\"wrap\">"," &copy; 2012-2014 <a href=\"http://cloudfi.re\">Cloudfire Systems</a>"," </div>","</div>"].join("\n");
});
// addProject.mustache
@ -405,10 +404,16 @@
module.exports = ["<div id=\"title\">"," <div class=\"wrap\">"," <h2>Disposable Project</h2>"," <span class=\"milestone\">Milestone 1.0</span>"," <p class=\"description\">The one where we deliver all that we promised.</p>"," </div>","</div>","","<div id=\"content\" class=\"wrap\">"," <Hero/>"," <Projects/>","</div>"].join("\n");
});
// showChart.mustache
root.require.register('burnchart/src/templates/pages/showChart.js', function(exports, require, module) {
module.exports = ["<div id=\"content\" class=\"wrap\">"," <div id=\"chart\">"," <div id=\"tooltip\"></div>"," <div id=\"svg\"></div>"," </div>","</div>"].join("\n");
});
// projects.mustache
root.require.register('burnchart/src/templates/projects.js', function(exports, require, module) {
module.exports = ["{{#projects.list}}"," <div id=\"projects\">"," <div class=\"header\">"," <a href=\"#\" class=\"sort\"><span class=\"icon sort-alphabet\"></span> Sorted by priority</a>"," <h2>Projects</h2>"," </div>",""," <table>"," {{#projects.list}}"," <tr>"," <td><a class=\"repo\" href=\"#\">{{owner}}/{{name}}</a></td>"," {{# { milestone: getMilestone(milestones.list) } }}"," {{#milestone}}"," <td>"," <span class=\"milestone\">"," {{ milestone.title }}"," <span class=\"icon down-open\">"," </span>"," </td>"," <td>"," <div class=\"progress\">"," <span class=\"percent\">{{Math.floor(format.progress(closed_issues, open_issues))}}%</span>"," <span class=\"due\">due {{format.fromNow(due_on)}}</span>"," <div class=\"outer bar\">"," <div class=\"inner bar {{format.onTime(milestone)}}\" style=\"width:{{format.progress(closed_issues, open_issues)}}%\"></div>"," </div>"," </div>"," </td>"," {{/milestone}}"," {{^milestone}}"," <td colspan=\"2\"><span class=\"milestone\"><em>No milestones yet</em></td>"," {{/milestone}}"," {{/}}"," </tr>"," {{/projects.list}}",""," <tr>"," <td><a class=\"repo\" href=\"#\">radekstepan/disposable</a></td>"," <td><span class=\"milestone\">Milestone 1.0 <span class=\"icon down-open\"></span></td>"," <td>"," <div class=\"progress\">"," <span class=\"percent\">40%</span>"," <span class=\"due\">due on Friday</span>"," <div class=\"outer bar\">"," <div class=\"inner bar red\" style=\"width:40%\"></div>"," </div>"," </div>"," </td>"," </tr>"," <tr class=\"done\">"," <td><a class=\"repo\" href=\"#\">radekstepan/burnchart</a></td>"," <td><span class=\"milestone\">Beta Milestone <span class=\"icon down-open\"></span></a></td>"," <td>"," <div class=\"progress\">"," <span class=\"percent\">100%</span>"," <span class=\"due\">due tomorrow</span>"," <div class=\"outer bar\">"," <div class=\"inner bar green\" style=\"width:100%\"></div>"," </div>"," </div>"," </td>"," </tr>"," <tr>"," <td><a class=\"repo\" href=\"#\">intermine/intermine</a></td>"," <td><span class=\"milestone\">Emma Release 96 <span class=\"icon down-open\"></span></a></td>"," <td>"," <div class=\"progress\">"," <span class=\"percent\">27%</span>"," <span class=\"due\">due in 2 weeks</span>"," <div class=\"outer bar\">"," <div class=\"inner bar red\" style=\"width:27%\"></div>"," </div>"," </div>"," </td>"," </tr>"," <tr>"," <td><a class=\"repo\" href=\"#\">microsoft/windows</a></td>"," <td><span class=\"milestone\">RC 9 <span class=\"icon down-open\"></span></a></td>"," <td>"," <div class=\"progress\">"," <span class=\"percent\">90%</span>"," <span class=\"due red\">overdue by a month</span>"," <div class=\"outer bar\">"," <div class=\"inner bar red\" style=\"width:90%\"></div>"," </div>"," </div>"," </td>"," </tr>"," </table>",""," <div class=\"footer\">"," <a href=\"#\"><span class=\"icon cog\"></span> Edit</a>"," </div>"," </div>","{{/projects.list}}"].join("\n");
module.exports = ["{{#projects.list.length}}"," <div id=\"projects\">"," <div class=\"header\">"," <a href=\"#\" class=\"sort\"><span class=\"icon sort-alphabet\"></span> Sorted by priority</a>"," <h2>Projects</h2>"," </div>",""," <table>"," {{#projects.list}}"," {{#milestones}}"," <tr>"," <td><a class=\"repo\">{{owner}}/{{name}}</a></td>"," <td>"," <a class=\"milestone\" href=\"#chart/{{owner}}/{{name}}/{{number}}\">{{ title }}</a>"," </td>"," <td>"," <div class=\"progress\">"," <span class=\"percent\">{{Math.floor(format.progress(closed_issues, open_issues))}}%</span>"," <span class=\"due\">due {{format.fromNow(due_on)}}</span>"," <div class=\"outer bar\">"," <div class=\"inner bar {{format.onTime(this)}}\" style=\"width:{{format.progress(closed_issues, open_issues)}}%\"></div>"," </div>"," </div>"," </td>"," </tr>"," {{/milestones}}"," {{/projects.list}}",""," <!--"," <tr>"," <td><a class=\"repo\" href=\"#\">radekstepan/disposable</a></td>"," <td><span class=\"milestone\">Milestone 1.0 <span class=\"icon down-open\"></span></td>"," <td>"," <div class=\"progress\">"," <span class=\"percent\">40%</span>"," <span class=\"due\">due on Friday</span>"," <div class=\"outer bar\">"," <div class=\"inner bar red\" style=\"width:40%\"></div>"," </div>"," </div>"," </td>"," </tr>"," <tr class=\"done\">"," <td><a class=\"repo\" href=\"#\">radekstepan/burnchart</a></td>"," <td><span class=\"milestone\">Beta Milestone <span class=\"icon down-open\"></span></a></td>"," <td>"," <div class=\"progress\">"," <span class=\"percent\">100%</span>"," <span class=\"due\">due tomorrow</span>"," <div class=\"outer bar\">"," <div class=\"inner bar green\" style=\"width:100%\"></div>"," </div>"," </div>"," </td>"," </tr>"," <tr>"," <td><a class=\"repo\" href=\"#\">intermine/intermine</a></td>"," <td><span class=\"milestone\">Emma Release 96 <span class=\"icon down-open\"></span></a></td>"," <td>"," <div class=\"progress\">"," <span class=\"percent\">27%</span>"," <span class=\"due\">due in 2 weeks</span>"," <div class=\"outer bar\">"," <div class=\"inner bar red\" style=\"width:27%\"></div>"," </div>"," </div>"," </td>"," </tr>"," <tr>"," <td><a class=\"repo\" href=\"#\">microsoft/windows</a></td>"," <td><span class=\"milestone\">RC 9 <span class=\"icon down-open\"></span></a></td>"," <td>"," <div class=\"progress\">"," <span class=\"percent\">90%</span>"," <span class=\"due red\">overdue by a month</span>"," <div class=\"outer bar\">"," <div class=\"inner bar red\" style=\"width:90%\"></div>"," </div>"," </div>"," </td>"," </tr>"," -->"," </table>",""," <div class=\"footer\">"," <a href=\"#\"><span class=\"icon cog\"></span> Edit</a>"," </div>"," </div>","{{/projects.list}}"].join("\n");
});
// date.coffee
@ -583,10 +588,20 @@
Projects: Projects
},
'data': {
'format': format,
getMilestone: function(list) {
return _.findWhere(list, 'active');
}
format: format
}
});
});
// showChart.coffee
root.require.register('burnchart/src/views/pages/showChart.js', function(exports, require, module) {
module.exports = Ractive.extend({
'template': require('../../templates/pages/showChart'),
'adapt': [Ractive.adaptors.Ractive],
init: function() {
return console.log(this.get('route'));
}
});

View File

@ -12,11 +12,12 @@ el = '#page'
route = (page, req, evt) ->
document.title = 'BurnChart: GitHub Burndown Chart as a Service'
Page = require "./views/pages/#{page}"
new Page { el }
new Page { el, 'data': { 'route': req.params } }
router =
'': _.partial route, 'index'
'project/add': _.partial route, 'addProject'
'': _.partial route, 'index'
'project/add': _.partial route, 'addProject'
'chart/:owner/:name/:milestone': _.partial route, 'showChart'
# TODO: remove in production.
'reset': ->
mediator.fire '!projects/clear'

View File

@ -11,14 +11,15 @@ module.exports = new Model
'list': []
init: ->
# Fetches a list of milestones for a repo.
getMilestones = ->
# Initialize with items stored locally.
localforage.getItem 'projects', (projects=[]) =>
@set 'list', projects
# Fetch milestones for each of these projects.
async.each projects, (project, cb) ->
mediator.fire '!projects/add', project
, (err) ->
throw err if err
# Persist in local storage.
# Persist projects in local storage.
@observe 'list', (projects) ->
localforage.setItem 'projects', projects
@ -28,24 +29,15 @@ module.exports = new Model
# Fetch milestones (which validates repo too).
request.allMilestones repo, (err, res) =>
throw err if err
return done err if err
# Pluck these fields for milestones.
milestones = _.pluckMany res, config.fields.milestone
# Set the default milestone as the soonest one with issues.
active = _.find milestones, (m) ->
0 < m.open_issues + m.closed_issues
# Push to the stack.
@push 'list', _.merge repo, { milestones }
active?.active = true
# Push to the stack
@push 'list', _.merge repo,
'milestones':
'list': milestones
'checked_at': do date.now # checked now
# Call back so we can redirect.
# Call back.
do done
mediator.on '!projects/clear', =>

View File

@ -233,7 +233,7 @@ ul
table
width: 100%
tr
tr
td
background: #FCFCFC
padding: 20px 30px

95
src/styles/chart.styl Normal file
View File

@ -0,0 +1,95 @@
@import 'nib'
// color definitions
$closed = #4DAF7C
$opened = #E55F3A
$grey = #CACACA
$brown = #64584C
$background1 = #D7BCAB
$background2 = #CC9485
// where D3 renders to
#chart
height: 200px
position: relative
// position will be adjusted dynamically
#tooltip
position: absolute
top: 0
left: 0
svg
path
&.line
fill: none
stroke-width: 1px
clip-path: url(#clip)
// actual progress
&.actual
stroke: $brown
stroke-width: 3px
// ideal velocity throughout the sprint
&.ideal
stroke: $grey
stroke-width: 3px
// trend of actual issue closures
&.trendline
stroke: $brown
stroke-width: 1.5px
stroke-dasharray: 5,5
// right now
line
&.today
stroke: $grey
stroke-width: 1px
shape-rendering: crispEdges
stroke-dasharray: 5,5
// represents one issue closed
circle
fill: $brown
// make it easier to click
stroke: transparent
stroke-width: 15px
cursor: pointer
// axes...
.axis
shape-rendering: crispEdges
line
stroke: rgba($grey, 0.25)
shape-rendering: crispEdges
text
font-weight: bold
fill: $grey
path
display: none
// tooltips
.d3-tip
margin-top: -10px
font-size: 11px
padding: 8px 10px 7px 10px
text-align: center
background: rgba(0,0,0,0.75)
color: #fff
border-radius: 3px
&:after
width: 100%
color: rgba(0,0,0,0.8)
content: "\25BC"
position: absolute
&.n:after
margin: -3px 0 0 0
top: 100%
left: 0

View File

@ -6,6 +6,6 @@
<div id="footer">
<div class="wrap">
&copy; 2012-2014 Radek Stepan
&copy; 2012-2014 <a href="http://cloudfi.re">Cloudfire Systems</a>
</div>
</div>

View File

@ -0,0 +1,6 @@
<div id="content" class="wrap">
<div id="chart">
<div id="tooltip"></div>
<div id="svg"></div>
</div>
</div>

View File

@ -1,4 +1,4 @@
{{#projects.list}}
{{#projects.list.length}}
<div id="projects">
<div class="header">
<a href="#" class="sort"><span class="icon sort-alphabet"></span> Sorted by priority</a>
@ -7,33 +7,26 @@
<table>
{{#projects.list}}
<tr>
<td><a class="repo" href="#">{{owner}}/{{name}}</a></td>
{{# { milestone: getMilestone(milestones.list) } }}
{{#milestone}}
{{#milestones}}
<tr>
<td><a class="repo">{{owner}}/{{name}}</a></td>
<td>
<span class="milestone">
{{ milestone.title }}
<span class="icon down-open">
</span>
<a class="milestone" href="#chart/{{owner}}/{{name}}/{{number}}">{{ title }}</a>
</td>
<td>
<div class="progress">
<span class="percent">{{Math.floor(format.progress(closed_issues, open_issues))}}%</span>
<span class="due">due {{format.fromNow(due_on)}}</span>
<div class="outer bar">
<div class="inner bar {{format.onTime(milestone)}}" style="width:{{format.progress(closed_issues, open_issues)}}%"></div>
<div class="inner bar {{format.onTime(this)}}" style="width:{{format.progress(closed_issues, open_issues)}}%"></div>
</div>
</div>
</td>
{{/milestone}}
{{^milestone}}
<td colspan="2"><span class="milestone"><em>No milestones yet</em></td>
{{/milestone}}
{{/}}
</tr>
</tr>
{{/milestones}}
{{/projects.list}}
<!--
<tr>
<td><a class="repo" href="#">radekstepan/disposable</a></td>
<td><span class="milestone">Milestone 1.0 <span class="icon down-open"></span></td>
@ -86,6 +79,7 @@
</div>
</td>
</tr>
-->
</table>
<div class="footer">

View File

@ -8,8 +8,4 @@ module.exports = Ractive.extend
'components': { Hero, Projects }
'data':
'format': format
# Find the milestone that is active.
getMilestone: (list) ->
_.findWhere list, 'active'
'data': { format }

View File

@ -0,0 +1,8 @@
module.exports = Ractive.extend
'template': require '../../templates/pages/showChart'
'adapt': [ Ractive.adaptors.Ractive ]
init: ->
console.log @get 'route'