diff --git a/TODO.md b/TODO.md
index 24860f5..43a5a94 100644
--- a/TODO.md
+++ b/TODO.md
@@ -15,6 +15,7 @@
###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
diff --git a/public/js/app.bundle.js b/public/js/app.bundle.js
index b99e6f8..6ba4244 100644
--- a/public/js/app.bundle.js
+++ b/public/js/app.bundle.js
@@ -39141,17 +39141,16 @@ Router.prototype.mount = function(routes, path) {
return user.reset();
},
onrender: function() {
- var client,
- _this = this;
+ var client;
this.set('client', client = new Firebase("https://" + config.data.firebase + ".firebaseio.com"));
return this.auth = new FirebaseSimpleLogin(client, function(err, obj) {
- user.set('loaded', true);
if (err) {
throw err;
}
if (obj) {
- return user.set(obj);
+ user.set(obj);
}
+ return user.set('ready', true);
});
}
});
@@ -39360,7 +39359,7 @@ Router.prototype.mount = function(routes, path) {
// request.coffee
root.require.register('burnchart/src/modules/github/request.js', function(exports, require, module) {
- var defaults, error, headers, request, response, user;
+ var defaults, error, headers, isReady, isValid, ready, request, response, stack, user;
user = require('../../models/user');
@@ -39385,54 +39384,92 @@ Router.prototype.mount = function(routes, path) {
module.exports = {
repo: function(_arg, cb) {
- var data, name, owner;
+ var name, owner;
owner = _arg.owner, name = _arg.name;
- data = _.defaults({
- 'path': "/repos/" + owner + "/" + name,
- 'headers': headers(user.data.token)
- }, defaults.github);
- return request(data, cb);
+ if (!isValid({
+ owner: owner,
+ name: name
+ })) {
+ return cb('Request is malformed');
+ }
+ return ready(function() {
+ var data;
+ data = _.defaults({
+ 'path': "/repos/" + owner + "/" + name,
+ 'headers': headers(user.data.accessToken)
+ }, defaults.github);
+ return request(data, cb);
+ });
},
allMilestones: function(_arg, cb) {
- var data, name, owner;
+ var name, owner;
owner = _arg.owner, name = _arg.name;
- data = _.defaults({
- 'path': "/repos/" + owner + "/" + name + "/milestones",
- 'query': {
- 'state': 'open',
- 'sort': 'due_date',
- 'direction': 'asc'
- },
- 'headers': headers(user.data.token)
- }, defaults.github);
- return request(data, cb);
+ if (!isValid({
+ owner: owner,
+ name: name
+ })) {
+ return cb('Request is malformed');
+ }
+ return ready(function() {
+ var data;
+ data = _.defaults({
+ 'path': "/repos/" + owner + "/" + name + "/milestones",
+ 'query': {
+ 'state': 'open',
+ 'sort': 'due_date',
+ 'direction': 'asc'
+ },
+ 'headers': headers(user.data.accessToken)
+ }, defaults.github);
+ return request(data, cb);
+ });
},
oneMilestone: function(_arg, cb) {
- var data, milestone, name, owner;
+ var milestone, name, owner;
owner = _arg.owner, name = _arg.name, milestone = _arg.milestone;
- data = _.defaults({
- 'path': "/repos/" + owner + "/" + name + "/milestones/" + milestone,
- 'query': {
- 'state': 'open',
- 'sort': 'due_date',
- 'direction': 'asc'
- },
- 'headers': headers(user.data.token)
- }, defaults.github);
- return request(data, cb);
+ if (!isValid({
+ owner: owner,
+ name: name,
+ milestone: milestone
+ })) {
+ return cb('Request is malformed');
+ }
+ return ready(function() {
+ var data;
+ data = _.defaults({
+ 'path': "/repos/" + owner + "/" + name + "/milestones/" + milestone,
+ 'query': {
+ 'state': 'open',
+ 'sort': 'due_date',
+ 'direction': 'asc'
+ },
+ 'headers': headers(user.data.accessToken)
+ }, defaults.github);
+ return request(data, cb);
+ });
},
allIssues: function(_arg, query, cb) {
- var data, milestone, name, owner;
+ var milestone, name, owner;
owner = _arg.owner, name = _arg.name, milestone = _arg.milestone;
- data = _.defaults({
- 'path': "/repos/" + owner + "/" + name + "/issues",
- 'query': _.extend(query, {
- milestone: milestone,
- 'per_page': '100'
- }),
- 'headers': headers(user.data.token)
- }, defaults.github);
- return request(data, cb);
+ if (!isValid({
+ owner: owner,
+ name: name,
+ milestone: milestone
+ })) {
+ return cb('Request is malformed');
+ }
+ return ready(function() {
+ var data;
+ data = _.defaults({
+ 'path': "/repos/" + owner + "/" + name + "/issues",
+ 'query': _.extend(query, {
+ milestone: milestone,
+ 'per_page': '100'
+ }),
+ 'headers': headers(user.data.accessToken)
+ }, defaults.github);
+ return request(data, cb);
+ });
}
};
@@ -39484,16 +39521,62 @@ Router.prototype.mount = function(routes, path) {
headers = function(token) {
var h;
- h = _.extend({}, {
+ h = {
'Content-Type': 'application/json',
'Accept': 'application/vnd.github.v3'
- });
+ };
if (token != null) {
h.Authorization = "token " + token;
}
return h;
};
+ isValid = function(obj) {
+ var key, rules, val;
+ rules = {
+ 'owner': function(val) {
+ return val != null;
+ },
+ 'name': function(val) {
+ return val != null;
+ },
+ 'milestone': function(val) {
+ return _.isInt(val);
+ }
+ };
+ for (key in obj) {
+ val = obj[key];
+ if (key in rules && !rules[key](val)) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ isReady = user.data.ready;
+
+ stack = [];
+
+ ready = function(cb) {
+ if (isReady) {
+ return cb();
+ } else {
+ return stack.push(cb);
+ }
+ };
+
+ user.observe('ready', function(val) {
+ var _results;
+ isReady = val;
+ if (val) {
+ _results = [];
+ while (stack.length) {
+ _results.push(stack.shift()());
+ }
+ return _results;
+ }
+ });
+
error = function(err) {
var message;
switch (false) {
@@ -39758,7 +39841,7 @@ Router.prototype.mount = function(routes, path) {
// header.mustache
root.require.register('burnchart/src/templates/header.js', function(exports, require, module) {
- module.exports = ["
"," {{#with user}}"," {{#loaded}}","
"," {{#displayName}}"," {{displayName}} logged in"," {{else}}","
Sign In"," {{/displayName}}","
"," {{/loaded}}"," {{/with}}","","
"," "," ",""," ","","
","
"].join("\n");
+ module.exports = [""," {{#with user}}"," {{#ready}}","
"," {{#displayName}}"," {{displayName}} logged in"," {{else}}","
Sign In"," {{/displayName}}","
"," {{/ready}}"," {{/with}}","","
"," "," ",""," ","","
","
"].join("\n");
});
// hero.mustache
@@ -39907,6 +39990,9 @@ Router.prototype.mount = function(routes, path) {
});
return obj;
});
+ },
+ 'isInt': function(val) {
+ return !isNaN(val) && parseInt(Number(val)) === val && !isNaN(parseInt(val, 10));
}
});
@@ -40290,6 +40376,7 @@ Router.prototype.mount = function(routes, path) {
var done, fetchIssues, fetchMilestone, milestone, name, obj, owner, project, _ref,
_this = this;
_ref = this.get('route'), owner = _ref[0], name = _ref[1], milestone = _ref[2];
+ milestone = parseInt(milestone);
document.title = "" + owner + "/" + name + "/" + milestone;
project = projects.find({
owner: owner,
@@ -40309,13 +40396,19 @@ Router.prototype.mount = function(routes, path) {
}
done = system.async();
fetchMilestone = function(cb) {
- return milestones.fetch(_.extend(project, {
+ return milestones.fetch({
+ owner: owner,
+ name: name,
milestone: milestone
- }), cb);
+ }, cb);
};
- fetchIssues = function(milestone, cb) {
- return issues.fetchAll(project, function(err, obj) {
- return cb(err, _.extend(milestone, {
+ fetchIssues = function(data, cb) {
+ return issues.fetchAll({
+ owner: owner,
+ name: name,
+ milestone: milestone
+ }, function(err, obj) {
+ return cb(err, _.extend(data, {
'issues': obj
}));
});
@@ -40330,7 +40423,11 @@ Router.prototype.mount = function(routes, path) {
'ttl': null
});
}
- projects.push('list', data);
+ if (project.milestones == null) {
+ project.milestones = [];
+ }
+ project.milestones.push(data);
+ projects.update('list');
return _this.set({
'milestone': data,
'ready': true
diff --git a/public/js/app.js b/public/js/app.js
index 13c77c8..0dcf797 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -93,17 +93,16 @@
return user.reset();
},
onrender: function() {
- var client,
- _this = this;
+ var client;
this.set('client', client = new Firebase("https://" + config.data.firebase + ".firebaseio.com"));
return this.auth = new FirebaseSimpleLogin(client, function(err, obj) {
- user.set('loaded', true);
if (err) {
throw err;
}
if (obj) {
- return user.set(obj);
+ user.set(obj);
}
+ return user.set('ready', true);
});
}
});
@@ -312,7 +311,7 @@
// request.coffee
root.require.register('burnchart/src/modules/github/request.js', function(exports, require, module) {
- var defaults, error, headers, request, response, user;
+ var defaults, error, headers, isReady, isValid, ready, request, response, stack, user;
user = require('../../models/user');
@@ -337,54 +336,92 @@
module.exports = {
repo: function(_arg, cb) {
- var data, name, owner;
+ var name, owner;
owner = _arg.owner, name = _arg.name;
- data = _.defaults({
- 'path': "/repos/" + owner + "/" + name,
- 'headers': headers(user.data.token)
- }, defaults.github);
- return request(data, cb);
+ if (!isValid({
+ owner: owner,
+ name: name
+ })) {
+ return cb('Request is malformed');
+ }
+ return ready(function() {
+ var data;
+ data = _.defaults({
+ 'path': "/repos/" + owner + "/" + name,
+ 'headers': headers(user.data.accessToken)
+ }, defaults.github);
+ return request(data, cb);
+ });
},
allMilestones: function(_arg, cb) {
- var data, name, owner;
+ var name, owner;
owner = _arg.owner, name = _arg.name;
- data = _.defaults({
- 'path': "/repos/" + owner + "/" + name + "/milestones",
- 'query': {
- 'state': 'open',
- 'sort': 'due_date',
- 'direction': 'asc'
- },
- 'headers': headers(user.data.token)
- }, defaults.github);
- return request(data, cb);
+ if (!isValid({
+ owner: owner,
+ name: name
+ })) {
+ return cb('Request is malformed');
+ }
+ return ready(function() {
+ var data;
+ data = _.defaults({
+ 'path': "/repos/" + owner + "/" + name + "/milestones",
+ 'query': {
+ 'state': 'open',
+ 'sort': 'due_date',
+ 'direction': 'asc'
+ },
+ 'headers': headers(user.data.accessToken)
+ }, defaults.github);
+ return request(data, cb);
+ });
},
oneMilestone: function(_arg, cb) {
- var data, milestone, name, owner;
+ var milestone, name, owner;
owner = _arg.owner, name = _arg.name, milestone = _arg.milestone;
- data = _.defaults({
- 'path': "/repos/" + owner + "/" + name + "/milestones/" + milestone,
- 'query': {
- 'state': 'open',
- 'sort': 'due_date',
- 'direction': 'asc'
- },
- 'headers': headers(user.data.token)
- }, defaults.github);
- return request(data, cb);
+ if (!isValid({
+ owner: owner,
+ name: name,
+ milestone: milestone
+ })) {
+ return cb('Request is malformed');
+ }
+ return ready(function() {
+ var data;
+ data = _.defaults({
+ 'path': "/repos/" + owner + "/" + name + "/milestones/" + milestone,
+ 'query': {
+ 'state': 'open',
+ 'sort': 'due_date',
+ 'direction': 'asc'
+ },
+ 'headers': headers(user.data.accessToken)
+ }, defaults.github);
+ return request(data, cb);
+ });
},
allIssues: function(_arg, query, cb) {
- var data, milestone, name, owner;
+ var milestone, name, owner;
owner = _arg.owner, name = _arg.name, milestone = _arg.milestone;
- data = _.defaults({
- 'path': "/repos/" + owner + "/" + name + "/issues",
- 'query': _.extend(query, {
- milestone: milestone,
- 'per_page': '100'
- }),
- 'headers': headers(user.data.token)
- }, defaults.github);
- return request(data, cb);
+ if (!isValid({
+ owner: owner,
+ name: name,
+ milestone: milestone
+ })) {
+ return cb('Request is malformed');
+ }
+ return ready(function() {
+ var data;
+ data = _.defaults({
+ 'path': "/repos/" + owner + "/" + name + "/issues",
+ 'query': _.extend(query, {
+ milestone: milestone,
+ 'per_page': '100'
+ }),
+ 'headers': headers(user.data.accessToken)
+ }, defaults.github);
+ return request(data, cb);
+ });
}
};
@@ -436,16 +473,62 @@
headers = function(token) {
var h;
- h = _.extend({}, {
+ h = {
'Content-Type': 'application/json',
'Accept': 'application/vnd.github.v3'
- });
+ };
if (token != null) {
h.Authorization = "token " + token;
}
return h;
};
+ isValid = function(obj) {
+ var key, rules, val;
+ rules = {
+ 'owner': function(val) {
+ return val != null;
+ },
+ 'name': function(val) {
+ return val != null;
+ },
+ 'milestone': function(val) {
+ return _.isInt(val);
+ }
+ };
+ for (key in obj) {
+ val = obj[key];
+ if (key in rules && !rules[key](val)) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ isReady = user.data.ready;
+
+ stack = [];
+
+ ready = function(cb) {
+ if (isReady) {
+ return cb();
+ } else {
+ return stack.push(cb);
+ }
+ };
+
+ user.observe('ready', function(val) {
+ var _results;
+ isReady = val;
+ if (val) {
+ _results = [];
+ while (stack.length) {
+ _results.push(stack.shift()());
+ }
+ return _results;
+ }
+ });
+
error = function(err) {
var message;
switch (false) {
@@ -710,7 +793,7 @@
// header.mustache
root.require.register('burnchart/src/templates/header.js', function(exports, require, module) {
- module.exports = [""," {{#with user}}"," {{#loaded}}","
"," {{#displayName}}"," {{displayName}} logged in"," {{else}}","
Sign In"," {{/displayName}}","
"," {{/loaded}}"," {{/with}}","","
"," "," ",""," ","","
","
"].join("\n");
+ module.exports = [""," {{#with user}}"," {{#ready}}","
"," {{#displayName}}"," {{displayName}} logged in"," {{else}}","
Sign In"," {{/displayName}}","
"," {{/ready}}"," {{/with}}","","
"," "," ",""," ","","
","
"].join("\n");
});
// hero.mustache
@@ -859,6 +942,9 @@
});
return obj;
});
+ },
+ 'isInt': function(val) {
+ return !isNaN(val) && parseInt(Number(val)) === val && !isNaN(parseInt(val, 10));
}
});
@@ -1242,6 +1328,7 @@
var done, fetchIssues, fetchMilestone, milestone, name, obj, owner, project, _ref,
_this = this;
_ref = this.get('route'), owner = _ref[0], name = _ref[1], milestone = _ref[2];
+ milestone = parseInt(milestone);
document.title = "" + owner + "/" + name + "/" + milestone;
project = projects.find({
owner: owner,
@@ -1261,13 +1348,19 @@
}
done = system.async();
fetchMilestone = function(cb) {
- return milestones.fetch(_.extend(project, {
+ return milestones.fetch({
+ owner: owner,
+ name: name,
milestone: milestone
- }), cb);
+ }, cb);
};
- fetchIssues = function(milestone, cb) {
- return issues.fetchAll(project, function(err, obj) {
- return cb(err, _.extend(milestone, {
+ fetchIssues = function(data, cb) {
+ return issues.fetchAll({
+ owner: owner,
+ name: name,
+ milestone: milestone
+ }, function(err, obj) {
+ return cb(err, _.extend(data, {
'issues': obj
}));
});
@@ -1282,7 +1375,11 @@
'ttl': null
});
}
- projects.push('list', data);
+ if (project.milestones == null) {
+ project.milestones = [];
+ }
+ project.milestones.push(data);
+ projects.update('list');
return _this.set({
'milestone': data,
'ready': true
diff --git a/src/models/firebase.coffee b/src/models/firebase.coffee
index 11b15a8..1983ca4 100644
--- a/src/models/firebase.coffee
+++ b/src/models/firebase.coffee
@@ -26,10 +26,10 @@ module.exports = new Model
@set 'client', client = new Firebase "https://#{config.data.firebase}.firebaseio.com"
# Check if we have a user in session.
- @auth = new FirebaseSimpleLogin client, (err, obj) =>
- user.set 'loaded', yes
-
+ @auth = new FirebaseSimpleLogin client, (err, obj) ->
throw err if err
-
+
# Save user.
- user.set obj if obj
\ No newline at end of file
+ user.set obj if obj
+ # Say we are done.
+ user.set 'ready', yes
\ No newline at end of file
diff --git a/src/modules/github/request.coffee b/src/modules/github/request.coffee
index 8c46fbf..309c9a7 100644
--- a/src/modules/github/request.coffee
+++ b/src/modules/github/request.coffee
@@ -19,42 +19,54 @@ module.exports =
# Get a repo.
repo: ({ owner, name }, cb) ->
- data = _.defaults
- 'path': "/repos/#{owner}/#{name}"
- 'headers': headers user.data.token
- , defaults.github
+ return cb 'Request is malformed' unless isValid { owner, name }
- request data, cb
+ ready ->
+ data = _.defaults
+ 'path': "/repos/#{owner}/#{name}"
+ 'headers': headers user.data.accessToken
+ , defaults.github
+
+ request data, cb
# Get all open milestones.
- allMilestones: ({ owner, name }, cb) ->
- data = _.defaults
- 'path': "/repos/#{owner}/#{name}/milestones"
- 'query': { 'state': 'open', 'sort': 'due_date', 'direction': 'asc' }
- 'headers': headers user.data.token
- , defaults.github
+ allMilestones: ({ owner, name }, cb) ->
+ return cb 'Request is malformed' unless isValid { owner, name }
- request data, cb
+ ready ->
+ data = _.defaults
+ 'path': "/repos/#{owner}/#{name}/milestones"
+ 'query': { 'state': 'open', 'sort': 'due_date', 'direction': 'asc' }
+ 'headers': headers user.data.accessToken
+ , defaults.github
+
+ request data, cb
# Get one open milestone.
oneMilestone: ({ owner, name, milestone }, cb) ->
- data = _.defaults
- 'path': "/repos/#{owner}/#{name}/milestones/#{milestone}"
- 'query': { 'state': 'open', 'sort': 'due_date', 'direction': 'asc' }
- 'headers': headers user.data.token
- , defaults.github
+ return cb 'Request is malformed' unless isValid { owner, name, milestone }
- request data, cb
+ ready ->
+ data = _.defaults
+ 'path': "/repos/#{owner}/#{name}/milestones/#{milestone}"
+ 'query': { 'state': 'open', 'sort': 'due_date', 'direction': 'asc' }
+ 'headers': headers user.data.accessToken
+ , defaults.github
+
+ request data, cb
# Get all issues for a state.
- allIssues: ({ owner, name, milestone }, query, cb) ->
- data = _.defaults
- 'path': "/repos/#{owner}/#{name}/issues"
- 'query': _.extend query, { milestone, 'per_page': '100' }
- 'headers': headers user.data.token
- , defaults.github
+ allIssues: ({ owner, name, milestone }, query, cb) ->
+ return cb 'Request is malformed' unless isValid { owner, name, milestone }
- request data, cb
+ ready ->
+ data = _.defaults
+ 'path': "/repos/#{owner}/#{name}/issues"
+ 'query': _.extend query, { milestone, 'per_page': '100' }
+ 'headers': headers user.data.accessToken
+ , defaults.github
+
+ request data, cb
# Make a request using SuperAgent.
request = ({ protocol, host, path, query, headers }, cb) ->
@@ -99,13 +111,37 @@ response = (err, data, cb) ->
# Give us headers.
headers = (token) ->
# The defaults.
- h = _.extend {},
+ h =
'Content-Type': 'application/json'
'Accept': 'application/vnd.github.v3'
# Add token?
h.Authorization = "token #{token}" if token?
h
+isValid = (obj) ->
+ rules =
+ 'owner': (val) -> val?
+ 'name': (val) -> val?
+ 'milestone': (val) -> _.isInt val
+
+ ( return no for key, val of obj when key of rules and not rules[key](val) )
+
+ yes
+
+# Switch when user is ready.
+isReady = user.data.ready
+
+# A stack of requests to execute once ready.
+stack = []
+ready = (cb) ->
+ if isReady then do cb else stack.push cb
+
+# Observe user's readiness.
+user.observe 'ready', (val) ->
+ isReady = val
+ # Clear the stack?
+ ( do stack.shift() while stack.length ) if val
+
# Parse an error.
error = (err) ->
switch
diff --git a/src/templates/header.mustache b/src/templates/header.mustache
index 1a99d1a..0e9eeef 100644
--- a/src/templates/header.mustache
+++ b/src/templates/header.mustache
@@ -1,6 +1,6 @@
{{#with user}}
- {{#loaded}}
+ {{#ready}}
{{#displayName}}
{{displayName}} logged in
@@ -8,7 +8,7 @@
Sign In
{{/displayName}}
- {{/loaded}}
+ {{/ready}}
{{/with}}
diff --git a/src/utils/mixins.coffee b/src/utils/mixins.coffee
index 69808f9..e237ab5 100644
--- a/src/utils/mixins.coffee
+++ b/src/utils/mixins.coffee
@@ -5,4 +5,7 @@ _.mixin
obj = {}
_.each keys, (key) ->
obj[key] = item[key]
- obj
\ No newline at end of file
+ obj
+
+ 'isInt': (val) ->
+ not isNaN(val) and parseInt(Number(val)) is val and not isNaN(parseInt(val, 10))
\ No newline at end of file
diff --git a/src/views/pages/milestone.coffee b/src/views/pages/milestone.coffee
index d76664a..2e8fcb6 100644
--- a/src/views/pages/milestone.coffee
+++ b/src/views/pages/milestone.coffee
@@ -22,6 +22,8 @@ module.exports = Ractive.extend
onrender: ->
[ owner, name, milestone ] = @get 'route'
+ milestone = parseInt milestone
+
document.title = "#{owner}/#{name}/#{milestone}"
# Get the associated project.
@@ -38,11 +40,11 @@ module.exports = Ractive.extend
done = do system.async
fetchMilestone = (cb) ->
- milestones.fetch _.extend(project, { milestone }), cb
+ milestones.fetch { owner, name, milestone }, cb
- fetchIssues = (milestone, cb) ->
- issues.fetchAll project, (err, obj) ->
- cb err, _.extend milestone, { 'issues': obj }
+ fetchIssues = (data, cb) ->
+ issues.fetchAll { owner, name, milestone }, (err, obj) ->
+ cb err, _.extend data, { 'issues': obj }
async.waterfall [
# Get the milestone.
@@ -59,7 +61,9 @@ module.exports = Ractive.extend
} if err
# Save the milestone.
- projects.push 'list', data
+ project.milestones ?= []
+ project.milestones.push data
+ projects.update 'list'
# Show the page.
@set