save milestone on chart page

This commit is contained in:
Radek Stepan 2014-10-14 19:55:51 -07:00
parent 14882754ca
commit 714ab701b9
8 changed files with 103 additions and 72 deletions

32
TODO.md
View File

@ -2,37 +2,30 @@
##Release: MVP
- [ ] visiting a project page (from chart) should query for all milestones and issues sans what we've got
###GitHub
- [ ] sort milestones on index and project page based on priority (most delayed first)
- [ ] sort milestones on index and project page based on priority (most delayed first); Trend - actual = different in days and those overdue come first
###Notifications
- [ ] create a 500/400/loading system messages
- [ ] mediator `!app/notify/edit` will edit the current notification
- [ ] need to show status (receiving information etc.) per repo with little warning triangle when we get 400/500 on milestones
- [ ] handle multiple notifications, for example success on closed milestone and then show a different chart or add a project
###Error Handling
- [ ] second visit to a different milestone comes out blank
- [ ] verify that project exists on project page when fetching it remotely (add behind the scenes)
- [ ] deal with Firebase timing out, are we still logged-in?
- [ ] visiting a chart page saves the project if it isn't saved already
- [ ] check that we have not run out of requests to make
- [ ] can we get more than 1 notification at a time?
- [ ] can we get more than 1 notification at a time? stack them and show just one text
- [ ] save in memory only if no `localStorage`, warn about that
- [ ] what if milestone does not match our strategy?
- [ ] show a stack of errors from index page as notifications
###Gotchas
- [ ] an issue may have been closed before the start of the milestone; choose the earliest as the start date
- [ ] Check location.hash is supported
- [ ] move tests from `radekstepan/github-burndown-chart`
###Bugs
- [ ] `rails/rails/24` has issues in two clusters as if merged from two milestones
- [ ] trendline cutting into axes
###Docs
@ -46,12 +39,13 @@
###Style
- [ ] focus on form fields style
- [ ] focus on form fields style (blue outline etc)
- [ ] switch off `user-select` on buttons
- [ ] make async pages transition so that there is no "jumping" on the page
- [ ] index page alert tooltip
- [ ] index page alert tooltip (like on chart page)
- [ ] app icon like http://thenounproject.com/term/fire/50966/
- [ ] make it easy to go back to project page from a chart page, show it in the header
- [ ] tell people if they have no due date
###Misc
@ -79,16 +73,11 @@
- [ ] show animated lines when drawing the chart
- [ ] highlight changes from past fetch
- [ ] In add a project form autocomplete on my username, orgs I am member of and repos I have access to
- [ ] Someone might create a public repo, add it to the system and switch it to private; need to check repo priviledges at runtime; or when asking for auth, one would choose either public OR public/private, but this could get confusign.
- [ ] Make sure the padding fits throughout the interface; we have user-select on elements.
- [ ] Check location.hash is supported
- [ ] Have an app wide of triggering a URL and have named routes too
- [ ] On page load get all the latest data regardless of `time_ago`
- [ ] 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
- [ ] choose your own theme
- [ ] custom milestone start dates
- [ ] show burndown chart for all milestones
- [ ] handle Enterprise editions of GH (signed up in gh dev program)
- [ ] auto-update the chart (with delay when no activity) when logged-in
@ -102,13 +91,10 @@
- [ ] show velocity for all team members and how it progresses through time
- [ ] points collector - give medals for 1st 3 spots in terms of velocity
- [ ] show past commits or due dates like in [this calendar](https://dribbble.com/shots/1736128-Meetups-Page?list=shots&sort=popular&timeframe=now&offset=5)
- [ ] allow people to submit suggestions via GitHub Issues
- [ ] find a way where, as a group, we can share repo data by trusting the other repo members that use our platform
- [ ] support Jira & Gitlab
- [ ] when fetching subsequent updates, fetch only the last page of issues since some repos are large (2.5MB & 19 pages for `mbostock/d3`); actually that is for all issues, not milestone constrained. So only an issue if we want to see a burnchart for all the issues for a repo
- [ ] move tests from `radekstepan/github-burndown-chart`
- [ ] if all issue circles are close to each other, make a "master circle" that amalgamates all the issues into one large circle, makes for a prettier view
- [ ] tell people if they have no due date
- [ ] make better x-axis date display, otherwise we see all 1s.
- [ ] some [fun loading messages](http://www.gamefaqs.com/pc/561176-simcity-4/faqs/22135) from Sim City.
- [ ] show number of tasks, points, days left just like in Assembly
- [ ] show number of tasks, points, days left just like in Assembly on chart page

View File

@ -39185,6 +39185,18 @@ Router.prototype.mount = function(routes, path) {
return this.push('list', project);
}
},
addMilestone: function(project, milestone) {
var idx;
if ((idx = _.findIndex(this.data.list, project)) > -1) {
if (project.milestones != null) {
return this.push("list." + idx + ".milestones", milestone);
} else {
return this.set("list." + idx + ".milestones", [milestone]);
}
} else {
throw 500;
}
},
clear: function() {
return this.set('list', []);
},
@ -39263,7 +39275,7 @@ Router.prototype.mount = function(routes, path) {
root.require.register('burnchart/src/modules/chart/axes.js', function(exports, require, module) {
module.exports = {
horizontal: function(height) {
horizontal: function(height, x) {
return d3.svg.axis().scale(x).orient("bottom").tickSize(-height).tickFormat(function(d) {
return d.getDate();
}).tickPadding(10);
@ -40040,10 +40052,14 @@ Router.prototype.mount = function(routes, path) {
'name': 'views/chart',
'template': require('../templates/chart'),
oncomplete: function() {
var actual, height, ideal, issues, line, m, mAxis, margin, milestone, svg, tooltip, total, trend, width, x, xAxis, y, yAxis, _ref;
var actual, head, height, ideal, issues, line, m, mAxis, margin, milestone, svg, tooltip, total, trend, width, x, xAxis, y, yAxis, _ref;
milestone = this.data.milestone;
issues = milestone.issues;
total = issues.open.size + issues.closed.size;
head = issues.closed.list[0].closed_at;
if (issues.length && milestone.created_at > head) {
milestone.created_at = head;
}
actual = lines.actual(issues.closed.list, milestone.created_at, total);
ideal = lines.ideal(milestone.created_at, milestone.due_on, total);
trend = lines.trend(actual, milestone.created_at, milestone.due_on);
@ -40058,7 +40074,7 @@ Router.prototype.mount = function(routes, path) {
height -= margin.top + margin.bottom;
x = d3.time.scale().range([0, width]);
y = d3.scale.linear().range([height, 0]);
xAxis = axes.horizontal(height);
xAxis = axes.horizontal(height, x);
yAxis = axes.vertical(width, y);
line = d3.svg.line().interpolate("linear").x(function(d) {
return x(d.date);
@ -40439,11 +40455,7 @@ Router.prototype.mount = function(routes, path) {
'ttl': null
});
}
if (project.milestones == null) {
project.milestones = [];
}
project.milestones.push(data);
projects.update('list');
projects.addMilestone(project, data);
return _this.set({
'milestone': data,
'ready': true
@ -40535,7 +40547,7 @@ Router.prototype.mount = function(routes, path) {
'ready': false
},
onrender: function() {
var done, fetchIssues, fetchMilestones, name, owner, project, _ref,
var done, fetchIssues, fetchMilestones, findMilestone, name, owner, project, _ref,
_this = this;
_ref = this.get('route'), owner = _ref[0], name = _ref[1];
document.title = "" + owner + "/" + name;
@ -40546,27 +40558,36 @@ Router.prototype.mount = function(routes, path) {
if (!project) {
throw 500;
}
if (project.milestones) {
return this.set('ready', true);
}
done = system.async();
findMilestone = function(number) {
return _.find(project.milestones || [], {
number: number
});
};
fetchMilestones = function(cb) {
return milestones.fetchAll(project, cb);
};
fetchIssues = function(allMilestones, cb) {
return async.map(allMilestones, function(milestone, cb) {
return async.each(allMilestones, function(milestone, cb) {
if (findMilestone(milestone.number)) {
return cb(null);
}
return issues.fetchAll({
owner: owner,
name: name,
'milestone': milestone.number
}, function(err, obj) {
return cb(err, _.extend(milestone, {
if (err) {
return cb(err);
}
projects.addMilestone(project, _.extend(milestone, {
'issues': obj
}));
return cb(null);
});
}, cb);
};
return async.waterfall([fetchMilestones, fetchIssues], function(err, data) {
return async.waterfall([fetchMilestones, fetchIssues], function(err) {
done();
if (err) {
return mediator.fire('!app/notify', {
@ -40576,10 +40597,7 @@ Router.prototype.mount = function(routes, path) {
'ttl': null
});
}
return _this.set({
'project.milestones': data,
'ready': true
});
return _this.set('ready', true);
});
}
});

View File

@ -137,6 +137,18 @@
return this.push('list', project);
}
},
addMilestone: function(project, milestone) {
var idx;
if ((idx = _.findIndex(this.data.list, project)) > -1) {
if (project.milestones != null) {
return this.push("list." + idx + ".milestones", milestone);
} else {
return this.set("list." + idx + ".milestones", [milestone]);
}
} else {
throw 500;
}
},
clear: function() {
return this.set('list', []);
},
@ -215,7 +227,7 @@
root.require.register('burnchart/src/modules/chart/axes.js', function(exports, require, module) {
module.exports = {
horizontal: function(height) {
horizontal: function(height, x) {
return d3.svg.axis().scale(x).orient("bottom").tickSize(-height).tickFormat(function(d) {
return d.getDate();
}).tickPadding(10);
@ -992,10 +1004,14 @@
'name': 'views/chart',
'template': require('../templates/chart'),
oncomplete: function() {
var actual, height, ideal, issues, line, m, mAxis, margin, milestone, svg, tooltip, total, trend, width, x, xAxis, y, yAxis, _ref;
var actual, head, height, ideal, issues, line, m, mAxis, margin, milestone, svg, tooltip, total, trend, width, x, xAxis, y, yAxis, _ref;
milestone = this.data.milestone;
issues = milestone.issues;
total = issues.open.size + issues.closed.size;
head = issues.closed.list[0].closed_at;
if (issues.length && milestone.created_at > head) {
milestone.created_at = head;
}
actual = lines.actual(issues.closed.list, milestone.created_at, total);
ideal = lines.ideal(milestone.created_at, milestone.due_on, total);
trend = lines.trend(actual, milestone.created_at, milestone.due_on);
@ -1010,7 +1026,7 @@
height -= margin.top + margin.bottom;
x = d3.time.scale().range([0, width]);
y = d3.scale.linear().range([height, 0]);
xAxis = axes.horizontal(height);
xAxis = axes.horizontal(height, x);
yAxis = axes.vertical(width, y);
line = d3.svg.line().interpolate("linear").x(function(d) {
return x(d.date);
@ -1391,11 +1407,7 @@
'ttl': null
});
}
if (project.milestones == null) {
project.milestones = [];
}
project.milestones.push(data);
projects.update('list');
projects.addMilestone(project, data);
return _this.set({
'milestone': data,
'ready': true
@ -1487,7 +1499,7 @@
'ready': false
},
onrender: function() {
var done, fetchIssues, fetchMilestones, name, owner, project, _ref,
var done, fetchIssues, fetchMilestones, findMilestone, name, owner, project, _ref,
_this = this;
_ref = this.get('route'), owner = _ref[0], name = _ref[1];
document.title = "" + owner + "/" + name;
@ -1498,27 +1510,36 @@
if (!project) {
throw 500;
}
if (project.milestones) {
return this.set('ready', true);
}
done = system.async();
findMilestone = function(number) {
return _.find(project.milestones || [], {
number: number
});
};
fetchMilestones = function(cb) {
return milestones.fetchAll(project, cb);
};
fetchIssues = function(allMilestones, cb) {
return async.map(allMilestones, function(milestone, cb) {
return async.each(allMilestones, function(milestone, cb) {
if (findMilestone(milestone.number)) {
return cb(null);
}
return issues.fetchAll({
owner: owner,
name: name,
'milestone': milestone.number
}, function(err, obj) {
return cb(err, _.extend(milestone, {
if (err) {
return cb(err);
}
projects.addMilestone(project, _.extend(milestone, {
'issues': obj
}));
return cb(null);
});
}, cb);
};
return async.waterfall([fetchMilestones, fetchIssues], function(err, data) {
return async.waterfall([fetchMilestones, fetchIssues], function(err) {
done();
if (err) {
return mediator.fire('!app/notify', {
@ -1528,10 +1549,7 @@
'ttl': null
});
}
return _this.set({
'project.milestones': data,
'ready': true
});
return _this.set('ready', true);
});
}
});

View File

@ -18,6 +18,15 @@ module.exports = new Model
add: (project) ->
@push 'list', project unless @exists project
addMilestone: (project, milestone) ->
if (idx = _.findIndex(@data.list, project)) > -1
if project.milestones?
@push "list.#{idx}.milestones", milestone
else
@set "list.#{idx}.milestones", [ milestone ]
else
throw 500
clear: ->
@set 'list', []

View File

@ -1,6 +1,6 @@
module.exports =
horizontal: (height) ->
horizontal: (height, x) ->
d3.svg.axis().scale(x)
.orient("bottom")
# Show vertical lines...

View File

@ -13,10 +13,12 @@ module.exports = Ractive.extend
# Total number of points in the milestone.
total = issues.open.size + issues.closed.size
# An issue may have been closed before the start of a milestone.
if issues.length and milestone.created_at > issues[0].closed_at
head = issues.closed.list[0].closed_at
if issues.length and milestone.created_at > head
# This is the new start.
milestone.created_at = issues[0].closed_at
milestone.created_at = head
# Actual, ideal & trend lines.
actual = lines.actual issues.closed.list, milestone.created_at, total
@ -24,7 +26,7 @@ module.exports = Ractive.extend
trend = lines.trend actual, milestone.created_at, milestone.due_on
# Get available space.
{ height, width } = @el.getBoundingClientRect()
{ height, width } = do @el.getBoundingClientRect
margin = { 'top': 30, 'right': 30, 'bottom': 40, 'left': 50 }
width -= margin.left + margin.right
@ -35,9 +37,9 @@ module.exports = Ractive.extend
y = d3.scale.linear().range([ height, 0 ])
# Axes.
xAxis = axes.horizontal height
xAxis = axes.horizontal height, x
yAxis = axes.vertical width, y
# Line generator.
line = d3.svg.line()
.interpolate("linear")

View File

@ -60,10 +60,8 @@ module.exports = Ractive.extend
'ttl': null
} if err
# Save the milestone.
project.milestones ?= []
project.milestones.push data
projects.update 'list'
# Save the milestone with issues.
projects.addMilestone project, data
# Show the page.
@set

View File

@ -45,7 +45,7 @@ module.exports = Ractive.extend
issues.fetchAll { owner, name, 'milestone': milestone.number }, (err, obj) ->
return cb err if err
# Save the milestone with issues.
project.push 'milestones', _.extend milestone, { 'issues': obj }
projects.addMilestone project, _.extend milestone, { 'issues': obj }
cb null
, cb