From 230a012f0090c496fc5cdb011bcc8ed732fd0f5c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 3 Mar 2019 22:18:23 +0100 Subject: Add polls (#10111) * Add polls Fix #1629 * Add tests * Fixes * Change API for creating polls * Use name instead of content for votes * Remove poll validation for remote polls * Add polls to public pages * When updating the poll, update options just in case they were changed * Fix public pages showing both poll and other media --- app/models/poll_vote.rb | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 app/models/poll_vote.rb (limited to 'app/models/poll_vote.rb') diff --git a/app/models/poll_vote.rb b/app/models/poll_vote.rb new file mode 100644 index 000000000..57781d616 --- /dev/null +++ b/app/models/poll_vote.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: poll_votes +# +# id :bigint(8) not null, primary key +# account_id :bigint(8) +# poll_id :bigint(8) +# choice :integer default(0), not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class PollVote < ApplicationRecord + belongs_to :account + belongs_to :poll, inverse_of: :votes + + validates :choice, presence: true + validates_with VoteValidator + + after_create_commit :increment_counter_cache + + private + + def increment_counter_cache + poll.cached_tallies[choice] = (poll.cached_tallies[choice] || 0) + 1 + poll.save + end +end -- cgit From 833ffce2df68ae3b673e230fcb273da5d8c4681f Mon Sep 17 00:00:00 2001 From: ThibG Date: Mon, 4 Mar 2019 22:51:23 +0100 Subject: Store remote votes URI (#10158) * Store remote votes URI * Add spec for accepting remote votes * Make poll vote id generation work the same way as follows --- app/lib/activitypub/activity/create.rb | 2 +- app/models/poll_vote.rb | 3 +++ app/serializers/activitypub/vote_serializer.rb | 2 +- db/migrate/20190304152020_add_uri_to_poll_votes.rb | 5 +++++ db/schema.rb | 3 ++- spec/lib/activitypub/activity/create_spec.rb | 21 +++++++++++++++++++++ 6 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20190304152020_add_uri_to_poll_votes.rb (limited to 'app/models/poll_vote.rb') diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index fc4c45692..07ef16bf3 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -241,7 +241,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity def poll_vote? return false if replied_to_status.nil? || replied_to_status.poll.nil? || !replied_to_status.local? || !replied_to_status.poll.options.include?(@object['name']) - replied_to_status.poll.votes.create!(account: @account, choice: replied_to_status.poll.options.index(@object['name'])) + replied_to_status.poll.votes.create!(account: @account, choice: replied_to_status.poll.options.index(@object['name']), uri: @object['id']) end def resolve_thread(status) diff --git a/app/models/poll_vote.rb b/app/models/poll_vote.rb index 57781d616..aec678968 100644 --- a/app/models/poll_vote.rb +++ b/app/models/poll_vote.rb @@ -9,6 +9,7 @@ # choice :integer default(0), not null # created_at :datetime not null # updated_at :datetime not null +# uri :string # class PollVote < ApplicationRecord @@ -20,6 +21,8 @@ class PollVote < ApplicationRecord after_create_commit :increment_counter_cache + delegate :local?, to: :account + private def increment_counter_cache diff --git a/app/serializers/activitypub/vote_serializer.rb b/app/serializers/activitypub/vote_serializer.rb index 655d04d22..248190404 100644 --- a/app/serializers/activitypub/vote_serializer.rb +++ b/app/serializers/activitypub/vote_serializer.rb @@ -6,7 +6,7 @@ class ActivityPub::VoteSerializer < ActiveModel::Serializer :in_reply_to, :to def id - [ActivityPub::TagManager.instance.uri_for(object.account), '#votes/', object.id].join + ActivityPub::TagManager.instance.uri_for(object) || [ActivityPub::TagManager.instance.uri_for(object.account), '#votes/', object.id].join end def type diff --git a/db/migrate/20190304152020_add_uri_to_poll_votes.rb b/db/migrate/20190304152020_add_uri_to_poll_votes.rb new file mode 100644 index 000000000..f6b81f1ba --- /dev/null +++ b/db/migrate/20190304152020_add_uri_to_poll_votes.rb @@ -0,0 +1,5 @@ +class AddUriToPollVotes < ActiveRecord::Migration[5.2] + def change + add_column :poll_votes, :uri, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index d5d516e06..161619dcf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_02_26_003449) do +ActiveRecord::Schema.define(version: 2019_03_04_152020) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -447,6 +447,7 @@ ActiveRecord::Schema.define(version: 2019_02_26_003449) do t.integer "choice", default: 0, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "uri" t.index ["account_id"], name: "index_poll_votes_on_account_id" t.index ["poll_id"], name: "index_poll_votes_on_poll_id" end diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index ac6237c86..4780c29c8 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -447,6 +447,27 @@ RSpec.describe ActivityPub::Activity::Create do expect(poll.cached_tallies).to eq [10, 3] end end + + context 'when a vote to a local poll' do + let(:poll) { Fabricate(:poll, options: %w(Yellow Blue)) } + let!(:local_status) { Fabricate(:status, owned_poll: poll) } + + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + name: 'Yellow', + inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status) + } + end + + it 'adds a vote to the poll with correct uri' do + vote = poll.votes.first + expect(vote).to_not be_nil + expect(vote.uri).to eq object_json[:id] + expect(poll.reload.cached_tallies).to eq [1, 0] + end + end end context 'when sender is followed by local users' do -- cgit From a198add83bb527c32fa0e01404338562b157da99 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 5 Mar 2019 03:51:18 +0100 Subject: Fix various issues in polls (#10165) * Fix ActivityPub poll results being serialized even with hide_totals * Fix poll refresh button having a different font size * Display poll in OpenGraph description * Fix NoMethodError when serializing votes Regression from #10158 * Fix polls on public pages being broken for non-logged-in users * Do not show time remaining if poll has no expiration date --- app/helpers/stream_entries_helper.rb | 12 +++++++++++- app/javascript/mastodon/components/poll.js | 5 +++-- app/javascript/styles/mastodon/polls.scss | 1 + app/models/poll_vote.rb | 4 ++++ app/serializers/activitypub/note_serializer.rb | 12 ++++++++---- app/views/stream_entries/_poll.html.haml | 16 ++++++++++------ 6 files changed, 37 insertions(+), 13 deletions(-) (limited to 'app/models/poll_vote.rb') diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb index 7a74c0b7d..8392afa73 100644 --- a/app/helpers/stream_entries_helper.rb +++ b/app/helpers/stream_entries_helper.rb @@ -104,9 +104,19 @@ module StreamEntriesHelper I18n.t('statuses.content_warning', warning: status.spoiler_text) end + def poll_summary(status) + return unless status.poll + status.poll.options.map { |o| "[ ] #{o}" }.join("\n") + end + def status_description(status) components = [[media_summary(status), status_text_summary(status)].reject(&:blank?).join(' · ')] - components << status.text if status.spoiler_text.blank? + + if status.spoiler_text.blank? + components << status.text + components << poll_summary(status) + end + components.reject(&:blank?).join("\n\n") end diff --git a/app/javascript/mastodon/components/poll.js b/app/javascript/mastodon/components/poll.js index e9124aefa..182491af8 100644 --- a/app/javascript/mastodon/components/poll.js +++ b/app/javascript/mastodon/components/poll.js @@ -118,7 +118,7 @@ class Poll extends ImmutablePureComponent { /> {!showResults && } - {showResults && {Math.floor(percent)}%} + {showResults && {Math.round(percent)}%} {option.get('title')} @@ -146,7 +146,8 @@ class Poll extends ImmutablePureComponent {
{!showResults && } {showResults && !this.props.disabled && · } - · {timeRemaining} + + {poll.get('expires_at') && · {timeRemaining}}
); diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss index f42496559..7c6e61d63 100644 --- a/app/javascript/styles/mastodon/polls.scss +++ b/app/javascript/styles/mastodon/polls.scss @@ -82,6 +82,7 @@ border: 0; color: $dark-text-color; text-decoration: underline; + font-size: inherit; &:hover, &:focus, diff --git a/app/models/poll_vote.rb b/app/models/poll_vote.rb index aec678968..9ad66bbf8 100644 --- a/app/models/poll_vote.rb +++ b/app/models/poll_vote.rb @@ -23,6 +23,10 @@ class PollVote < ApplicationRecord delegate :local?, to: :account + def object_type + :vote + end + private def increment_counter_cache diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index b2c92fdc1..b2a5f53e0 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -15,8 +15,8 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer has_one :replies, serializer: ActivityPub::CollectionSerializer, if: :local? - has_many :poll_loaded_options, key: :one_of, if: :poll_and_not_multiple? - has_many :poll_loaded_options, key: :any_of, if: :poll_and_multiple? + has_many :poll_options, key: :one_of, if: :poll_and_not_multiple? + has_many :poll_options, key: :any_of, if: :poll_and_multiple? attribute :end_time, if: :poll_and_expires? attribute :closed, if: :poll_and_expired? @@ -121,8 +121,12 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer object.account.local? end - def poll_loaded_options - object.poll.loaded_options + def poll_options + if !object.expired? && object.hide_totals? + object.poll.unloaded_options + else + object.poll.loaded_options + end end def poll_and_multiple? diff --git a/app/views/stream_entries/_poll.html.haml b/app/views/stream_entries/_poll.html.haml index 974aff9bd..c7e5e0c63 100644 --- a/app/views/stream_entries/_poll.html.haml +++ b/app/views/stream_entries/_poll.html.haml @@ -1,5 +1,5 @@ -- options = (!poll.expired? && poll.hide_totals?) ? poll.unloaded_options : poll.loaded_options -- voted = poll.votes.where(account: current_user.account).exists? +- options = (!poll.expired? && poll.hide_totals?) ? poll.unloaded_options : poll.loaded_options +- voted = user_signed_in? && poll.votes.where(account: current_account).exists? - show_results = voted || poll.expired? .poll @@ -9,17 +9,21 @@ - if show_results - percent = 100 * option.votes_count / poll.votes_count %span.poll__chart{ style: "width: #{percent}%" } + %label.poll__text>< - %span.poll__number= percent + %span.poll__number= percent.round = option.title - else %label.poll__text>< - %span.poll__input{ class: poll.multiple ? 'checkbox' : nil}>< + %span.poll__input{ class: poll.multiple? ? 'checkbox' : nil}>< = option.title .poll__footer - unless show_results %button.button.button-secondary{ disabled: true } = t('statuses.poll.vote') + %span= t('statuses.poll.total_votes', count: poll.votes_count) - · - %span= poll.expires_at + + - unless poll.expires_at.nil? + · + %span= l poll.expires_at -- cgit