PERF: Refactor public polls users.
This commit is contained in:
parent
6827239444
commit
d80f340a36
12 changed files with 213 additions and 101 deletions
plugins/poll
assets
javascripts
components
poll-results-number-voters.js.es6poll-results-number.js.es6poll-results-standard-voters.js.es6poll-results-standard.js.es6poll-voters-number.js.es6poll-voters-standard.js.es6poll-voters.js.es6
discourse/templates/components
stylesheets/common
|
@ -1,15 +0,0 @@
|
|||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import PollVoters from 'discourse/plugins/poll/components/poll-voters';
|
||||
|
||||
export default PollVoters.extend({
|
||||
@computed("poll.voters", "pollsVoters")
|
||||
canLoadMore(voters, pollsVoters) {
|
||||
return pollsVoters.length < voters;
|
||||
},
|
||||
|
||||
@computed("poll.options", "offset")
|
||||
voterIds(options) {
|
||||
const ids = [].concat(...(options.map(option => option.voter_ids)));
|
||||
return this._getIds(ids);
|
||||
}
|
||||
});
|
|
@ -1,7 +1,24 @@
|
|||
import round from "discourse/lib/round";
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
|
||||
export default Em.Component.extend({
|
||||
didInsertElement() {
|
||||
this._super();
|
||||
this._fetchUsers();
|
||||
},
|
||||
|
||||
_fetchUsers() {
|
||||
if (!this.get('isPublic')) return;
|
||||
this.send("fetchUsers", this.get('voterIds').slice(0, 20));
|
||||
},
|
||||
|
||||
@computed('poll.options', 'poll.options.[]')
|
||||
voterIds(options) {
|
||||
const voterIds = _.uniq([].concat(...(options.map(option => option.get('voter_ids')))));
|
||||
return voterIds;
|
||||
},
|
||||
|
||||
@computed("poll.options.@each.{html,votes}")
|
||||
totalScore() {
|
||||
return _.reduce(this.get("poll.options"), function(total, o) {
|
||||
|
@ -22,4 +39,32 @@ export default Em.Component.extend({
|
|||
return I18n.t("poll.average_rating", { average: this.get("average") });
|
||||
},
|
||||
|
||||
actions: {
|
||||
fetchUsers(voterIds, defer) {
|
||||
const pollVoters = this.get('poll.pollVoters') || [];
|
||||
const ids = _.difference(voterIds, pollVoters.map(pollVoter => pollVoter.id));
|
||||
|
||||
if (ids.length > 0) {
|
||||
ajax("/polls/voters.json", {
|
||||
type: "put",
|
||||
data: { options: { default: ids } }
|
||||
}).then(result => {
|
||||
const voters = result.voters;
|
||||
const poll = this.get('poll');
|
||||
|
||||
poll.set('pollVoters', _.uniq(
|
||||
pollVoters.concat(voters['default']),
|
||||
user => user.id
|
||||
));
|
||||
|
||||
if (defer) defer.resolve();
|
||||
}).catch((error) => {
|
||||
Ember.Logger.error(error);
|
||||
bootbox.alert(I18n.t('poll.error_while_fetching_voters'));
|
||||
});
|
||||
} else {
|
||||
if (defer) defer.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import PollVoters from 'discourse/plugins/poll/components/poll-voters';
|
||||
|
||||
export default PollVoters.extend({
|
||||
@computed("option.votes", "pollsVoters")
|
||||
canLoadMore(voters, pollsVoters) {
|
||||
return pollsVoters.length < voters;
|
||||
},
|
||||
|
||||
@computed("option.voter_ids", "offset")
|
||||
voterIds(ids) {
|
||||
return this._getIds(ids);
|
||||
}
|
||||
});
|
|
@ -1,10 +1,38 @@
|
|||
import evenRound from "discourse/plugins/poll/lib/even-round";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import { default as computed, observes } from "ember-addons/ember-computed-decorators";
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
|
||||
export default Em.Component.extend({
|
||||
tagName: "ul",
|
||||
classNames: ["results"],
|
||||
|
||||
didInsertElement() {
|
||||
this._super();
|
||||
this._fetchUsers();
|
||||
},
|
||||
|
||||
_fetchUsers() {
|
||||
if (!this.get('isPublic')) return;
|
||||
this.send("fetchUsers", this.get('optionsVoterIds'));
|
||||
},
|
||||
|
||||
@observes('isPublic', 'poll.options.[]')
|
||||
updateNewVoters(isPublic) {
|
||||
if (!isPublic) return;
|
||||
this._fetchUsers();
|
||||
},
|
||||
|
||||
@computed('options', 'poll.options.[]')
|
||||
optionsVoterIds(options) {
|
||||
const ids = {};
|
||||
|
||||
options.forEach(option => {
|
||||
ids[option.get('id')] = option.get('voter_ids').slice(0, 20);
|
||||
});
|
||||
|
||||
return ids;
|
||||
},
|
||||
|
||||
@computed("poll.voters", "poll.type", "poll.options.[]")
|
||||
options(voters, type) {
|
||||
const options = this.get("poll.options").slice(0).sort((a, b) => {
|
||||
|
@ -31,10 +59,56 @@ export default Em.Component.extend({
|
|||
option.setProperties({
|
||||
percentage,
|
||||
style,
|
||||
title: I18n.t("poll.option_title", { count: option.get("votes") })
|
||||
title: I18n.t("poll.option_title", { count: option.get("votes") }),
|
||||
});
|
||||
});
|
||||
|
||||
return options;
|
||||
},
|
||||
|
||||
actions: {
|
||||
fetchUsers(optionsVoterIds, defer) {
|
||||
const ids = {};
|
||||
let updated = false;
|
||||
|
||||
this.get('options').forEach(option => {
|
||||
const optionId = option.get('id');
|
||||
const newIds = optionsVoterIds[optionId];
|
||||
const oldIds = (option.get('voters') || []).map(user => user.id);
|
||||
const diffIds = _.difference(newIds, oldIds);
|
||||
|
||||
if (diffIds.length > 0) {
|
||||
ids[optionId] = diffIds;
|
||||
updated = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (updated) {
|
||||
ajax("/polls/voters.json", {
|
||||
type: "put",
|
||||
data: { options: ids }
|
||||
}).then(result => {
|
||||
const voters = result.voters;
|
||||
|
||||
this.get('options').forEach(option => {
|
||||
const optionVoters = voters[option.get('id')];
|
||||
|
||||
if (!optionVoters) return;
|
||||
|
||||
option.set('voters', _.uniq(
|
||||
(option.get('voters') || []).concat(optionVoters),
|
||||
user => user.id
|
||||
));
|
||||
|
||||
if (defer) defer.resolve();
|
||||
});
|
||||
}).catch((error) => {
|
||||
Ember.Logger.error(error);
|
||||
bootbox.alert(I18n.t('poll.error_while_fetching_voters'));
|
||||
});
|
||||
} else {
|
||||
if (defer) defer.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
layoutName: "components/poll-voters",
|
||||
classNames: ["poll-voters"],
|
||||
offset: 1,
|
||||
loading: false,
|
||||
voters: Ember.computed.alias('poll.pollVoters'),
|
||||
|
||||
@computed('poll.voters', 'poll.pollVoters')
|
||||
canLoadMore(voters, pollVoters) {
|
||||
return (!pollVoters) ? false : pollVoters.length < voters;
|
||||
},
|
||||
|
||||
actions: {
|
||||
loadMore() {
|
||||
this.set('loading', true);
|
||||
const defer = Em.RSVP.defer();
|
||||
|
||||
defer.promise.then(() => {
|
||||
this.set('loading', false);
|
||||
this.incrementProperty('offset');
|
||||
});
|
||||
|
||||
const offset = this.get('offset');
|
||||
this.sendAction('fetch', this.get('voterIds').slice(20 * offset, 20 * (offset + 1)), defer);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
layoutName: "components/poll-voters",
|
||||
classNames: ["poll-voters"],
|
||||
offset: 1,
|
||||
loading: false,
|
||||
voters: Ember.computed.alias('option.voters'),
|
||||
|
||||
@computed('option.votes', 'option.voters')
|
||||
canLoadMore(votes, voters) {
|
||||
return (!voters) ? false : voters.length < votes;
|
||||
},
|
||||
|
||||
actions: {
|
||||
loadMore() {
|
||||
this.set('loading', true);
|
||||
|
||||
const defer = Em.RSVP.defer();
|
||||
defer.promise.then(() => {
|
||||
this.set('loading', false);
|
||||
this.incrementProperty('offset');
|
||||
});
|
||||
|
||||
const offset = this.get('offset');
|
||||
const ids = this.get('option.voter_ids');
|
||||
const optionVoterIds = {};
|
||||
optionVoterIds[this.get('option.id')] = ids.slice(20 * offset, 20 * (offset + 1));
|
||||
|
||||
this.sendAction('fetch', optionVoterIds, defer);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,52 +0,0 @@
|
|||
import { ajax } from 'discourse/lib/ajax';
|
||||
export default Ember.Component.extend({
|
||||
layoutName: "components/poll-voters",
|
||||
tagName: 'ul',
|
||||
classNames: ["poll-voters-list"],
|
||||
isExpanded: false,
|
||||
numOfVotersToShow: 0,
|
||||
offset: 0,
|
||||
loading: false,
|
||||
pollsVoters: null,
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
this.set("pollsVoters", []);
|
||||
},
|
||||
|
||||
_fetchUsers() {
|
||||
this.set("loading", true);
|
||||
|
||||
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();
|
||||
|
||||
this.set("numOfVotersToShow", Math.round(this.$().width() / 25) * 2);
|
||||
if (this.get("voterIds").length > 0) this._fetchUsers();
|
||||
},
|
||||
|
||||
actions: {
|
||||
loadMore() {
|
||||
this._fetchUsers();
|
||||
}
|
||||
}
|
||||
});
|
|
@ -3,5 +3,5 @@
|
|||
</div>
|
||||
|
||||
{{#if isPublic}}
|
||||
{{poll-results-number-voters poll=poll}}
|
||||
{{poll-voters-number voterIds=voterIds poll=poll fetch='fetchUsers'}}
|
||||
{{/if}}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</div>
|
||||
|
||||
{{#if isPublic}}
|
||||
{{poll-results-standard-voters option=option}}
|
||||
{{poll-voters-standard option=option fetch='fetchUsers'}}
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/each}}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<div class="poll-voters">
|
||||
{{#each pollsVoters as |user|}}
|
||||
<li>
|
||||
<a data-user-card={{unbound user.username}}>
|
||||
<ul class="poll-voters-list">
|
||||
{{#each voters as |user|}}
|
||||
<li class="poll-voters-list-item">
|
||||
<a data-user-card={{unbound user.username}}>
|
||||
{{avatar user imageSize="tiny" ignoreTitle="true"}}
|
||||
</a>
|
||||
</a>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
||||
{{#if canLoadMore}}
|
||||
<div class="poll-voters-toggle-expand">
|
||||
{{#if canLoadMore}}
|
||||
{{#conditional-loading-spinner condition=loading size="small"}}
|
||||
<a {{action "loadMore"}}>{{fa-icon "chevron-down"}}</a>
|
||||
{{/conditional-loading-spinner}}
|
||||
{{/if}}
|
||||
{{#conditional-loading-spinner condition=loading size="small"}}
|
||||
<a {{action "loadMore"}}>{{fa-icon "chevron-down"}}</a>
|
||||
{{/conditional-loading-spinner}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
|
@ -83,6 +83,14 @@ div.poll {
|
|||
.poll-voters-toggle-expand {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
|
||||
.inline-spinner {
|
||||
display: block;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.results {
|
||||
|
|
|
@ -211,20 +211,24 @@ after_initialize do
|
|||
end
|
||||
|
||||
def voters
|
||||
user_ids = params.require(:user_ids)
|
||||
options = params.require(:options)
|
||||
user_ids = options.values.flatten.uniq
|
||||
users = Hash[User.where(id: user_ids).map { |user| [user.id.to_s, UserNameSerializer.new(user).serializable_hash] }]
|
||||
|
||||
users = User.where(id: user_ids).map do |user|
|
||||
UserNameSerializer.new(user).serializable_hash
|
||||
voters = {}
|
||||
|
||||
options.each do |key, value|
|
||||
voters[key] = value.map { |user_id| users[user_id] }
|
||||
end
|
||||
|
||||
render json: { users: users }
|
||||
render json: { voters: voters }
|
||||
end
|
||||
end
|
||||
|
||||
DiscoursePoll::Engine.routes.draw do
|
||||
put "/vote" => "polls#vote"
|
||||
put "/toggle_status" => "polls#toggle_status"
|
||||
get "/voters" => 'polls#voters'
|
||||
put "/voters" => 'polls#voters'
|
||||
end
|
||||
|
||||
Discourse::Application.routes.append do
|
||||
|
|
Reference in a new issue