mirror of
https://github.com/status-im/burnchart.git
synced 2025-02-17 04:46:30 +00:00
save milestone on chart page
This commit is contained in:
parent
14882754ca
commit
714ab701b9
32
TODO.md
32
TODO.md
@ -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
|
@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -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', []
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
module.exports =
|
||||
|
||||
horizontal: (height) ->
|
||||
horizontal: (height, x) ->
|
||||
d3.svg.axis().scale(x)
|
||||
.orient("bottom")
|
||||
# Show vertical lines...
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user