FEATURE: Add Instant Run-off Voting to Poll Plugin (Part 1: migrate existing plugin to Glimmer only) (#27204)

The "migration to Glimmer" has been broken out here from #27155 to make the review process less onerous and reduce change risk: 

* DEV: migrates most of the widget code to Glimmer in prep for IRV additions
* NB This already incorporates significant amounts of review and feedback from the prior PR.
* NB because there was significant additional feedback relating to older Poll code that I've improved with feedback, there are some additional changes here that are general improvements to the plugin and not specific to IRV nor Glimmer!
* There should be no trace of IRV code here.

Once this is finalised and merged we can continue to progress with #27155.
This commit is contained in:
Robert
2024-07-04 12:34:48 +01:00
committed by GitHub
parent 32c8bcc3af
commit e3b6be15b8
28 changed files with 2169 additions and 1711 deletions

View File

@ -1,6 +1,9 @@
# frozen_string_literal: true
class DiscoursePoll::Poll
MULTIPLE = "multiple"
REGULAR = "regular"
def self.vote(user, post_id, poll_name, options)
poll_id = nil
@ -9,6 +12,7 @@ class DiscoursePoll::Poll
poll_id = poll.id
# remove options that aren't available in the poll
available_options = poll.poll_options.map { |o| o.digest }.to_set
options.select! { |o| available_options.include?(o) }
if options.empty?
@ -35,17 +39,21 @@ class DiscoursePoll::Poll
PollVote.where(poll: poll, user: user).where.not(poll_option_id: new_option_ids).delete_all
# create missing votes
(new_option_ids - old_option_ids).each do |option_id|
creation_set = new_option_ids - old_option_ids
creation_set.each do |option_id|
PollVote.create!(poll: poll, user: user, poll_option_id: option_id)
end
end
# Ensure consistency here as we do not have a unique index to limit the
# number of votes per the poll's configuration.
is_multiple = serialized_poll[:type] == "multiple"
is_multiple = serialized_poll[:type] == MULTIPLE
offset = is_multiple ? (serialized_poll[:max] || serialized_poll[:options].length) : 1
DB.query(<<~SQL, poll_id: poll_id, user_id: user.id, offset: offset)
params = { poll_id: poll_id, offset: offset, user_id: user.id }
DB.query(<<~SQL, params)
DELETE FROM poll_votes
USING (
SELECT
@ -61,6 +69,18 @@ class DiscoursePoll::Poll
AND poll_votes.user_id = to_delete_poll_votes.user_id
SQL
if serialized_poll[:type] == MULTIPLE
serialized_poll[:options].each do |option|
option.merge!(
chosen:
PollVote
.joins(:poll_option)
.where(poll_options: { digest: option[:id] }, user_id: user.id, poll_id: poll_id)
.exists?,
)
end
end
[serialized_poll, options]
end
@ -159,7 +179,9 @@ class DiscoursePoll::Poll
result = { option_digest => user_hashes }
else
votes = DB.query <<~SQL
params = { poll_id: poll.id, offset: offset, offset_plus_limit: offset + limit }
votes = DB.query(<<~SQL, params)
SELECT digest, user_id
FROM (
SELECT digest
@ -167,10 +189,10 @@ class DiscoursePoll::Poll
, ROW_NUMBER() OVER (PARTITION BY poll_option_id ORDER BY pv.created_at) AS row
FROM poll_votes pv
JOIN poll_options po ON pv.poll_option_id = po.id
WHERE pv.poll_id = #{poll.id}
AND po.poll_id = #{poll.id}
WHERE pv.poll_id = :poll_id
AND po.poll_id = :poll_id
) v
WHERE row BETWEEN #{offset} AND #{offset + limit}
WHERE row BETWEEN :offset AND :offset_plus_limit
SQL
user_ids = votes.map(&:user_id).uniq
@ -292,7 +314,7 @@ class DiscoursePoll::Poll
post_id: post_id,
name: poll["name"].presence || "poll",
close_at: close_at,
type: poll["type"].presence || "regular",
type: poll["type"].presence || REGULAR,
status: poll["status"].presence || "open",
visibility: poll["public"] == "true" ? "everyone" : "secret",
title: poll["title"],