Skip to content

Commit

Permalink
Merge pull request #35 from FriendsOfFlarum/cw/stop-loading-everything
Browse files Browse the repository at this point in the history
Use vote_count attributes instead of loading all votes
  • Loading branch information
clarkwinkelmann authored Apr 8, 2021
2 parents 143a935 + c6a6709 commit bf350a4
Show file tree
Hide file tree
Showing 32 changed files with 302 additions and 133 deletions.
14 changes: 10 additions & 4 deletions extend.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/*
* This file is part of fof/polls.
*
* Copyright (c) 2019 FriendsOfFlarum.
* Copyright (c) FriendsOfFlarum.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
Expand Down Expand Up @@ -60,11 +60,17 @@
->addInclude('poll'),

(new Extend\ApiController(Controller\ShowDiscussionController::class))
->addInclude(['poll', 'poll.options', 'poll.votes', 'poll.votes.user', 'poll.votes.option']),
->addInclude(['poll', 'poll.options', 'poll.myVotes', 'poll.myVotes.option'])
->addOptionalInclude(['poll.votes', 'poll.votes.user', 'poll.votes.option']),

(new Extend\ApiController(Controller\CreateDiscussionController::class))
->addInclude(['poll', 'poll.options', 'poll.votes', 'poll.votes.user', 'poll.votes.option']),
->addInclude(['poll', 'poll.options', 'poll.myVotes', 'poll.myVotes.option'])
->addOptionalInclude(['poll.votes', 'poll.votes.user', 'poll.votes.option']),

(new Extend\ApiController(Controller\UpdateDiscussionController::class))
->addInclude(['poll', 'poll.options', 'poll.votes', 'poll.votes.user', 'poll.votes.option']),
->addInclude(['poll', 'poll.options', 'poll.myVotes', 'poll.myVotes.option'])
->addOptionalInclude(['poll.votes', 'poll.votes.user', 'poll.votes.option']),

(new Extend\Console())
->command(Console\RefreshVoteCountCommand::class),
];
60 changes: 33 additions & 27 deletions js/src/forum/addPollToDiscussion.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,66 @@
import { extend } from 'flarum/extend';
import CommentPost from 'flarum/components/CommentPost';
import Stream from 'flarum/utils/Stream';
import DiscussionPoll from './components/DiscussionPoll';

// import PollVote from './components/PollVote';

export default () => {
extend(CommentPost.prototype, 'content', function (content) {
const discussion = this.attrs.post.discussion();

if (discussion.poll() && this.attrs.post.number() === 1) {
content.push(
DiscussionPoll.component({
discussion,
poll: discussion.poll(),
})
);
}
});

extend(CommentPost.prototype, 'oncreate', function (call, vnode) {
if (app.pusher) {
app.pusher.then((channels) => {
channels.main.bind('newPollVote', (data) => {
var userId = parseInt(data['user_id']);
extend(CommentPost.prototype, 'oninit', function () {
this.subtree.check(() => {
const discussion = this.attrs.post.discussion();

if (app.session.user && userId == app.session.user.id()) return;
if (!discussion.poll() || this.attrs.post.number() !== 1) {
return '';
}

let poll = app.store.getById('polls', this.attrs.post.discussion().poll().id());
// Make the post redraw everytime the poll or option vote count changed, or when the user vote changed
return JSON.stringify([
discussion.poll().voteCount(),
(discussion.poll().myVotes() || []).map(vote => vote.option().id()),
discussion.poll().options().map(option => option.voteCount()),
]);
});
});

if (parseInt(poll.id()) === parseInt(data['poll_id'])) {
let vote = {};
extend(CommentPost.prototype, 'oncreate', function (call, vnode) {
if (app.pusher) {
app.pusher.then((channels) => {
// We will listen for updates to all polls and options
// Even if that model is not in the current discussion, it doesn't really matter
channels.main.bind('updatedPollOption', (data) => {
const poll = app.store.getById('polls', data['pollId']);

Object.keys(data).map((key) => {
vote[key] = Stream(data[key]);
if (poll) {
poll.pushAttributes({
voteCount: data['pollVoteCount'],
});

vote['option'] = Stream(app.store.getById('poll_options', data['option_id']));
vote['user'] = Stream(app.store.getById('users', data['user_id']));
// Not redrawing here, as the option below should trigger the redraw already
}

let newVotes = poll.votes();
const option = app.store.getById('poll_options', data['optionId']);

newVotes.some((vote, i) => {
if (parseInt(vote.user() && vote.user().id()) === userId) {
newVotes.splice(i, 1);
}
if (option) {
option.pushAttributes({
voteCount: data['optionVoteCount'],
});

newVotes.push(vote);

poll.votes = Stream(newVotes);

m.redraw.sync();
m.redraw();
}
});

extend(vnode, 'onremove', () => channels.main.unbind('newPollVote'));
extend(vnode, 'onremove', () => channels.main.unbind('updatedPollOption'));
});
}
});
Expand Down
67 changes: 20 additions & 47 deletions js/src/forum/components/DiscussionPoll.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,36 @@
import Component from 'flarum/Component';
import Button from 'flarum/components/Button';
import LogInModal from 'flarum/components/LogInModal';
import Stream from 'flarum/utils/Stream';
import ListVotersModal from './ListVotersModal';

export default class DiscussionPoll extends Component {
oninit(vnode) {
super.oninit(vnode);
this.poll = this.attrs.poll;

this.vote = Stream();
this.voted = Stream(false);

this.updateData();
}

view() {
const hasVoted = this.myVotes.length > 0;

return (
<div>
<h3>{this.poll.question()}</h3>

{this.options.map((opt) => {
const hasVoted = this.voted();
const voted = this.vote() && this.vote().option().id() === opt.id();
const votes = this.votes.filter((v) => v.option().id() === opt.id()).length;
const percent = Math.round((votes / this.poll.votes().length) * 100);

const attrs = voted
? {
title:
hasVoted && app.translator.transChoice('fof-polls.forum.tooltip.votes', votes, { count: String(votes) }).join(''),
oncreate: function (vnode) {
$(vnode.dom).tooltip({ placement: 'right' });
},
}
: {};

const inputAttrs = voted
? {
checked: true,
}
: {};
const voted = this.myVotes.some(vote => vote.option() === opt);
const votes = opt.voteCount();
const percent = Math.round((votes / this.poll.voteCount()) * 100);

const title = hasVoted && app.translator.transChoice('fof-polls.forum.tooltip.votes', votes, {count: String(votes)}).join('');

return (
<div className={`PollOption ${hasVoted && 'PollVoted'} ${this.poll.hasEnded() && 'PollEnded'}`}>
<div {...attrs} className="PollBar" data-selected={voted}>
<div title={title} className="PollBar" data-selected={voted}>
{((!this.poll.hasEnded() && app.session.user && app.session.user.canVotePolls()) || !app.session.user) && (
<label className="checkbox">
<input onchange={this.changeVote.bind(this, opt)} type="checkbox" {...inputAttrs} />
<input onchange={this.changeVote.bind(this, opt)} type="checkbox" checked={voted} />
<span className="checkmark" />
</label>
)}
Expand Down Expand Up @@ -95,13 +78,8 @@ export default class DiscussionPoll extends Component {
}

updateData() {
this.poll = app.store.getById('polls', this.poll.id());
this.options = this.poll.options() || [];
this.votes = this.poll.votes() || [];

this.vote(app.session.user ? this.votes.find((v) => v.user() && v.user().id() === app.session.user.id()) : null);

this.voted(!!this.vote());
this.myVotes = this.poll.myVotes() || [];
}

onError(evt, error) {
Expand All @@ -117,13 +95,8 @@ export default class DiscussionPoll extends Component {
return;
}

if (this.vote() && option.id() === this.vote().option().id()) option = null;

if (!this.vote()) {
this.vote(app.store.createRecord('poll_votes'));

this.vote().pollId(this.poll.id());
}
// if we click on our current vote, we want to "un-vote"
if (this.myVotes.some(vote => vote.option() === option)) option = null;

app.request({
method: 'PATCH',
Expand All @@ -137,20 +110,20 @@ export default class DiscussionPoll extends Component {
}).then((res) => {
app.store.pushPayload(res);

if (!option) app.store.remove(this.vote());

this.updateData();

if (!option) {
m.redraw.sync();
}
m.redraw();
});
}

showVoters() {
app.modal.show(ListVotersModal, {
poll: this.poll,
});
// Load all the votes only when opening the votes list
app.store.find('discussions', this.attrs.discussion.id(), {
include: 'poll.votes,poll.votes.user,poll.votes.option',
}).then(() => {
app.modal.show(ListVotersModal, {
poll: this.poll,
});
})
}
}
2 changes: 2 additions & 0 deletions js/src/forum/models/Poll.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ export default class Poll extends mixin(Model, {
hasEnded: Model.attribute('hasEnded'),
endDate: Model.attribute('endDate'),
publicPoll: Model.attribute('publicPoll'),
voteCount: Model.attribute('voteCount'),

options: Model.hasMany('options'),
votes: Model.hasMany('votes'),
myVotes: Model.hasMany('myVotes'),
}) {
apiEndpoint() {
return `/fof/polls${this.exists ? `/${this.data.id}` : ''}`;
Expand Down
1 change: 1 addition & 0 deletions js/src/forum/models/PollOption.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import mixin from 'flarum/utils/mixin';

export default class PollOption extends mixin(Model, {
answer: Model.attribute('answer'),
voteCount: Model.attribute('voteCount'),

poll: Model.hasOne('polls'),
votes: Model.hasMany('votes'),
Expand Down
2 changes: 1 addition & 1 deletion migrations/2019_07_01_000000_add_polls_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/*
* This file is part of fof/polls.
*
* Copyright (c) 2019 FriendsOfFlarum.
* Copyright (c) FriendsOfFlarum.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
Expand Down
2 changes: 1 addition & 1 deletion migrations/2019_07_01_000001_add_poll_options_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/*
* This file is part of fof/polls.
*
* Copyright (c) 2019 FriendsOfFlarum.
* Copyright (c) FriendsOfFlarum.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
Expand Down
2 changes: 1 addition & 1 deletion migrations/2019_07_01_000002_add_poll_votes_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/*
* This file is part of fof/polls.
*
* Copyright (c) 2019 FriendsOfFlarum.
* Copyright (c) FriendsOfFlarum.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
Expand Down
16 changes: 16 additions & 0 deletions migrations/2021_04_06_000000_alter_options_add_vote_count.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

/*
* This file is part of fof/polls.
*
* Copyright (c) FriendsOfFlarum.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Flarum\Database\Migration;

return Migration::addColumns('poll_options', [
'vote_count' => ['integer', 'unsigned' => true, 'default' => 0],
]);
16 changes: 16 additions & 0 deletions migrations/2021_04_06_000001_alter_polls_add_vote_count.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

/*
* This file is part of fof/polls.
*
* Copyright (c) FriendsOfFlarum.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Flarum\Database\Migration;

return Migration::addColumns('polls', [
'vote_count' => ['integer', 'unsigned' => true, 'default' => 0],
]);
2 changes: 1 addition & 1 deletion src/Api/Controllers/DeletePollController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/*
* This file is part of fof/polls.
*
* Copyright (c) 2019 FriendsOfFlarum.
* Copyright (c) FriendsOfFlarum.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
Expand Down
2 changes: 1 addition & 1 deletion src/Api/Controllers/EditPollController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/*
* This file is part of fof/polls.
*
* Copyright (c) 2019 FriendsOfFlarum.
* Copyright (c) FriendsOfFlarum.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
Expand Down
6 changes: 4 additions & 2 deletions src/Api/Controllers/VotePollController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/*
* This file is part of fof/polls.
*
* Copyright (c) 2019 FriendsOfFlarum.
* Copyright (c) FriendsOfFlarum.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
Expand All @@ -26,7 +26,9 @@ class VotePollController extends AbstractShowController
*/
public $serializer = PollSerializer::class;

public $include = ['options', 'votes', 'votes.option', 'votes.user'];
public $include = ['options', 'myVotes', 'myVotes.option'];

public $optionalInclude = ['votes', 'votes.option', 'votes.user'];

/**
* @var Dispatcher
Expand Down
3 changes: 2 additions & 1 deletion src/Api/Serializers/PollOptionSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/*
* This file is part of fof/polls.
*
* Copyright (c) 2019 FriendsOfFlarum.
* Copyright (c) FriendsOfFlarum.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
Expand Down Expand Up @@ -32,6 +32,7 @@ protected function getDefaultAttributes($option)
{
return [
'answer' => $option->answer,
'voteCount' => (int) $option->vote_count,
'createdAt' => $this->formatDate($option->created_at),
'updatedAt' => $this->formatDate($option->updated_at),
];
Expand Down
Loading

0 comments on commit bf350a4

Please sign in to comment.