Merge pull request #4257 from tgxworld/paginate_public_polls

PERF: Paginate public polls.
This commit is contained in:
Guo Xiang Tan 2016-06-13 12:08:03 +08:00 committed by GitHub
commit 494f1f9fae
15 changed files with 102 additions and 91 deletions

View File

@ -3,29 +3,14 @@ import User from 'discourse/models/user';
import PollVoters from 'discourse/plugins/poll/components/poll-voters';
export default PollVoters.extend({
@computed("pollsVoters", "poll.options", "showMore", "isExpanded", "numOfVotersToShow")
users(pollsVoters, options, showMore, isExpanded, numOfVotersToShow) {
var users = [];
var voterIds = [];
const shouldLimit = showMore && !isExpanded;
options.forEach(option => {
option.voter_ids.forEach(voterId => {
if (shouldLimit) {
if (!(users.length > numOfVotersToShow - 1)) {
users.push(pollsVoters[voterId]);
}
} else {
users.push(pollsVoters[voterId]);
}
})
});
return users;
@computed("poll.voters", "pollsVoters")
canLoadMore(voters, pollsVoters) {
return pollsVoters.length < voters;
},
@computed("pollsVoters", "numOfVotersToShow")
showMore(pollsVoters, numOfVotersToShow) {
return !(Object.keys(pollsVoters).length < numOfVotersToShow);
@computed("poll.options", "offset")
voterIds(options) {
const ids = [].concat(...(options.map(option => option.voter_ids)));
return this._getIds(ids);
}
});

View File

@ -2,8 +2,6 @@ import round from "discourse/lib/round";
import computed from 'ember-addons/ember-computed-decorators';
export default Em.Component.extend({
tagName: "span",
@computed("poll.options.@each.{html,votes}")
totalScore() {
return _.reduce(this.get("poll.options"), function(total, o) {

View File

@ -3,23 +3,13 @@ import User from 'discourse/models/user';
import PollVoters from 'discourse/plugins/poll/components/poll-voters';
export default PollVoters.extend({
@computed("pollsVoters", "option.voter_ids", "showMore", "isExpanded", "numOfVotersToShow")
users(pollsVoters, voterIds, showMore, isExpanded, numOfVotersToShow) {
var users = [];
if (showMore && !isExpanded) {
voterIds = voterIds.slice(0, numOfVotersToShow);
}
voterIds.forEach(voterId => {
users.push(pollsVoters[voterId]);
});
return users;
@computed("option.votes", "pollsVoters")
canLoadMore(voters, pollsVoters) {
return pollsVoters.length < voters;
},
@computed("option.votes", "numOfVotersToShow")
showMore(numOfVotes, numOfVotersToShow) {
return !(numOfVotes < numOfVotersToShow);
@computed("option.voter_ids", "offset")
voterIds(ids) {
return this._getIds(ids);
}
});

View File

@ -3,11 +3,51 @@ export default Ember.Component.extend({
tagName: 'ul',
classNames: ["poll-voters-list"],
isExpanded: false,
numOfVotersToShow: 20,
numOfVotersToShow: 0,
offset: 0,
loading: false,
pollsVoters: null,
init() {
this._super();
this.set("pollsVoters", []);
},
_fetchUsers() {
this.set("loading", true);
Discourse.ajax("/polls/voters.json", {
type: "get",
data: { user_ids: this.get("voterIds") }
}).then(result => {
if (this.isDestroyed) return;
this.set("pollsVoters", this.get("pollsVoters").concat(result.users));
this.incrementProperty("offset");
this.set("loading", false);
}).catch((error) => {
Ember.logger.log(error);
bootbox.alert(I18n.t('poll.error_while_fetching_voters'));
});
},
_getIds(ids) {
const numOfVotersToShow = this.get("numOfVotersToShow");
const offset = this.get("offset");
return ids.slice(numOfVotersToShow * offset, numOfVotersToShow * (offset + 1));
},
didInsertElement() {
this._super();
Ember.run.schedule("afterRender", () => {
this.set("numOfVotersToShow", Math.round(this.$().width() / 25) * 2);
if (this.get("voterIds").length > 0) this._fetchUsers();
});
},
actions: {
toggleExpand() {
this.toggleProperty("isExpanded");
loadMore() {
this._fetchUsers();
}
}
});

View File

@ -6,7 +6,6 @@ export default Ember.Controller.extend({
isRandom : Ember.computed.equal("poll.order", "random"),
isClosed: Ember.computed.equal("poll.status", "closed"),
isPublic: Ember.computed.equal("poll.public", "true"),
pollsVoters: Ember.computed.alias("post.polls_voters"),
// shows the results when
// - poll is closed
@ -152,10 +151,6 @@ export default Ember.Controller.extend({
this.setProperties({ vote: votes, showResults: true });
this.set("model", Em.Object.create(poll));
if (poll.public) {
this.get("pollsVoters")[currentUser.get("id")] = currentUser;
}
}).catch(() => {
bootbox.alert(I18n.t("poll.error_while_casting_votes"));
}).finally(() => {

View File

@ -1,5 +1,7 @@
{{{averageRating}}}
<div class="poll-results-number-rating">
{{{averageRating}}}
</div>
{{#if poll.public}}
{{poll-results-number-voters poll=poll pollsVoters=pollsVoters}}
{{poll-results-number-voters poll=poll}}
{{/if}}

View File

@ -11,7 +11,7 @@
</div>
{{#if poll.public}}
{{poll-results-standard-voters option=option pollsVoters=pollsVoters}}
{{poll-results-standard-voters option=option}}
{{/if}}
</li>
{{/each}}

View File

@ -1,5 +1,5 @@
<div class="poll-voters">
{{#each users as |user|}}
{{#each pollsVoters as |user|}}
<li>
<a data-user-card={{unbound user.username}}>
{{avatar user imageSize="tiny" ignoreTitle="true"}}
@ -8,12 +8,10 @@
{{/each}}
<div class="poll-voters-toggle-expand">
{{#if showMore}}
{{#if isExpanded}}
<a {{action "toggleExpand"}}>{{fa-icon "chevron-up"}}</a>
{{else}}
<a {{action "toggleExpand"}}>{{fa-icon "chevron-down"}}</a>
{{/if}}
{{#if canLoadMore}}
{{#conditional-loading-spinner condition=loading size="small"}}
<a {{action "loadMore"}}>{{fa-icon "chevron-down"}}</a>
{{/conditional-loading-spinner}}
{{/if}}
</div>
</div>

View File

@ -2,9 +2,9 @@
<div class="poll-container">
{{#if showingResults}}
{{#if isNumber}}
{{poll-results-number poll=poll pollsVoters=pollsVoters}}
{{poll-results-number poll=poll}}
{{else}}
{{poll-results-standard poll=poll pollsVoters=pollsVoters}}
{{poll-results-standard poll=poll}}
{{/if}}
{{else}}
<ul>

View File

@ -29,10 +29,6 @@ function initializePolls(api) {
const post = this.get('model.postStream').findLoadedPost(msg.post_id);
if (post) {
post.set('polls', msg.polls);
if (msg.user) {
post.set(`polls_voters.${msg.user.id}`, msg.user);
}
}
});
},
@ -80,7 +76,6 @@ function initializePolls(api) {
const post = helper.getModel();
api.preventCloak(post.id);
const votes = post.get('polls_votes') || {};
post.set("polls_voters", (post.get("polls_voters") || {}));
post.pollsChanged();

View File

@ -81,7 +81,7 @@ div.poll {
vertical-align: middle;
padding: 10px;
& > span {
.poll-results-number-rating {
font-size: 2em;
}
}
@ -97,10 +97,11 @@ div.poll {
display: inline;
}
margin: 5px 0;
margin-top: 10px;
}
.poll-voters-toggle-expand {
width: 100%;
text-align: center;
}

View File

@ -67,3 +67,4 @@ en:
error_while_toggling_status: "There was an error while toggling the status of this poll."
error_while_casting_votes: "There was an error while casting your votes."
error_while_fetching_voters: "There was an error while displaying the voters."

View File

@ -185,7 +185,7 @@ after_initialize do
class DiscoursePoll::PollsController < ::ApplicationController
requires_plugin PLUGIN_NAME
before_filter :ensure_logged_in
before_filter :ensure_logged_in, except: [:voters]
def vote
post_id = params.require(:post_id)
@ -214,11 +214,22 @@ after_initialize do
render_json_error e.message
end
end
def voters
user_ids = params.require(:user_ids)
users = User.where(id: user_ids).map do |user|
UserNameSerializer.new(user).serializable_hash
end
render json: { users: users }
end
end
DiscoursePoll::Engine.routes.draw do
put "/vote" => "polls#vote"
put "/toggle_status" => "polls#toggle_status"
get "/voters" => 'polls#voters'
end
Discourse::Application.routes.append do
@ -299,26 +310,4 @@ after_initialize do
return unless post_custom_fields[DiscoursePoll::VOTES_CUSTOM_FIELD].present?
post_custom_fields[DiscoursePoll::VOTES_CUSTOM_FIELD].has_key?("#{scope.user.id}")
end
add_to_serializer(:post, :polls_voters) do
voters = {}
user_ids = post_custom_fields[DiscoursePoll::VOTES_CUSTOM_FIELD].keys
User.where(id: user_ids).map do |user|
voters[user.id] = UserNameSerializer.new(user).serializable_hash
end
voters
end
add_to_serializer(:post, :include_polls_voters?) do
return unless post_custom_fields.present?
return unless post_custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD].present?
return unless post_custom_fields[DiscoursePoll::VOTES_CUSTOM_FIELD].present?
post_custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD].any? do |_, value|
value["public"] == "true"
end
end
end

View File

@ -173,5 +173,4 @@ describe ::DiscoursePoll::PollsController do
end
end
end

View File

@ -0,0 +1,18 @@
require "rails_helper"
describe "DiscoursePoll endpoints" do
describe "fetch voters from user_ids" do
let(:user) { Fabricate(:user) }
it "should return the right response" do
get "/polls/voters.json", { user_ids: [user.id] }
expect(response.status).to eq(200)
json = JSON.parse(response.body)["users"].first
expect(json["name"]).to eq(user.name)
expect(json["title"]).to eq(user.title)
end
end
end