Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions tests/test_api_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@ def test_api_labels_without_auth(self):
self.assertEqual(rv.status_code, 200)
self.assertEqual(rv.content_type, 'application/json')

def test_api_comments_link_header_auth(self):
'''API access to comments greater than 30 returns pagination in Link header of the response.'''
query_string = {'callback': 'foo'}

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

'''Force a JSONP callback response because it gives us the header properties we want to test.'''
rv = self.app.get('/api/issues/398/comments', query_string=query_string, environ_base=headers)
self.assertTrue = all(x in rv.data for x in ['Link', 'rel', 'next', 'last', 'page'])
self.assertEqual(rv.status_code, 200)
self.assertEqual(rv.content_type, 'application/json')
'''API access to comments less than 30 does not return any link header in the response.'''
rv = self.app.get('/api/issues/4/comments', query_string=query_string, environ_base=headers)
self.assertTrue = not all(x in rv.data for x in ['Link', 'rel', 'next', 'last', 'page'])
self.assertEqual(rv.status_code, 200)
self.assertEqual(rv.content_type, 'application/json')

def test_api_set_labels_without_auth(self):
'''API setting labels without auth returns JSON 403 error code.'''
rv = self.app.post('/api/issues/1/labels',
Expand Down
11 changes: 10 additions & 1 deletion webcompat/static/js/lib/comments.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@ var issues = issues || {};
issues.CommentsCollection = Backbone.Collection.extend({
model: issues.Comment,
url: function() {
return '/api/issues/' + issueNumber + '/comments?per_page=100';
return '/api/issues/' + issueNumber + '/comments?page=' + this.pageNumber;
},
initialize: function(options) {
this.pageNumber = options.pageNumber;
},
fetchPage: function(options) {
if (options.pageNumber) {
this.pageNumber = options.pageNumber;
}
return this.fetch(options);
}
});

Expand Down
20 changes: 16 additions & 4 deletions webcompat/static/js/lib/issues.js
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ issues.MainView = Backbone.View.extend({
$(document.body).addClass('language-html');
var issueNum = {number: issueNumber};
this.issue = new issues.Issue(issueNum);
this.comments = new issues.CommentsCollection([]);
this.comments = new issues.CommentsCollection({pageNumber: 1});
this.initSubViews();
this.fetchModels();
},
Expand Down Expand Up @@ -426,17 +426,18 @@ issues.MainView = Backbone.View.extend({

// If there are any comments, go fetch the model data
if (this.issue.get('commentNumber') > 0) {
this.comments.fetch(headersBag).success(_.bind(function() {
this.comments.fetch(headersBag).success(_.bind(function(response) {
this.addExistingComments();
// the add event is fired when a model is added to the collection.
this.comments.bind('add', _.bind(this.addComment, this));

// If there's a #hash pointing to a comment (or elsewhere)
// scrollTo it.
if (location.hash !== '') {
var _id = $(location.hash);
window.scrollTo(0, _id.offset().top);
}
if (response[0].lastPageNumber > 1) {
this.getRemainingComments(++response[0].lastPageNumber);
}
}, this)).error(function() {
var msg = 'There was an error retrieving issue comments. Please reload to try again.';
wcEvents.trigger('flash:error', {message: msg, timeout: 4000});
Expand All @@ -453,6 +454,17 @@ issues.MainView = Backbone.View.extend({
}
});
},

getRemainingComments: function(count) {
//The first 30 comments for page 1 has already been loaded.
//If more than 30 comments are there the remaining comments are rendered in sets of 30
//in consecutive pages

_.each(_.range(2, count), function(i) {
this.comments.fetchPage({pageNumber: i, headers: {'Accept': 'application/json'}});
}, this);
},

addComment: function(comment) {
// if there's a nsfw label, add the whatever class.
var view = new issues.CommentView({model: comment});
Expand Down
41 changes: 40 additions & 1 deletion webcompat/static/js/lib/models/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ issues.Comment = Backbone.Model.extend({
url: function() {
return '/api/issues/' + issueNumber + '/comments';
},
parse: function(response) {
parse: function(response, jqXHR) {
this.set({
avatarUrl: response.user.avatar_url,
body: md.render(response.body),
Expand All @@ -25,5 +25,44 @@ issues.Comment = Backbone.Model.extend({
createdAt: moment(response.created_at).fromNow(),
rawBody: response.body
});
var linkHeader = jqXHR.xhr.getResponseHeader('Link');
if (linkHeader !== null && !!this.parseHeader(linkHeader).last) {
response.lastPageNumber = this.parseHeader(linkHeader).last.split('\?page\=')[1];
}
else {
response.lastPageNumber = '1';
}
},
parseHeader: function(linkHeader) {
//TODO: Abstract 'parseHeader' method from comment.js in to a mixin
//See Issue #1118
/* Returns an object like so:
{
next: "comments?page=3",
last: "comments?page=4",
first: "comments?page=1",
prev: "comments?page=1"
} */
var result = {};
var entries = linkHeader.split(',');
var relsRegExp = /\brel="?([^"]+)"?\s*;?/;
var keysRegExp = /(\b[0-9a-z\.-]+\b)/g;
var sourceRegExp = /^<(.*)>/;

for (var i = 0; i < entries.length; i++) {
var entry = entries[i].trim();
var rels = relsRegExp.exec(entry);
if (rels) {
var keys = rels[1].match(keysRegExp);
var source = sourceRegExp.exec(entry)[1];
var k;
var kLength = keys.length;
for (k = 0; k < kLength; k += 1) {
result[keys[k]] = source;
}
}
}

return result;
}
});