Skip to content

Commit

Permalink
FEATURE: Add 'groups' option to polls (#8469)
Browse files Browse the repository at this point in the history
This options can be used to restrict polls to certain groups.
  • Loading branch information
nbianca committed Jan 28, 2020
1 parent a9d0d55 commit 07222af
Show file tree
Hide file tree
Showing 12 changed files with 118 additions and 21 deletions.
1 change: 1 addition & 0 deletions plugins/poll/app/models/poll.rb
Expand Up @@ -78,6 +78,7 @@ def can_see_voters?(user)
# created_at :datetime not null
# updated_at :datetime not null
# chart_type :integer default("bar"), not null
# groups :string
#
# Indexes
#
Expand Down
7 changes: 6 additions & 1 deletion plugins/poll/app/serializers/poll_serializer.rb
Expand Up @@ -13,7 +13,8 @@ class PollSerializer < ApplicationSerializer
:voters,
:close,
:preloaded_voters,
:chart_type
:chart_type,
:groups

def public
true
Expand All @@ -35,6 +36,10 @@ def include_step?
object.step.present? && object.number?
end

def include_groups?
groups.present?
end

def options
object.poll_options.map { |o| PollOptionSerializer.new(o, root: false).as_json }
end
Expand Down
11 changes: 11 additions & 0 deletions plugins/poll/assets/javascripts/controllers/poll-ui-builder.js.es6
Expand Up @@ -82,6 +82,13 @@ export default Controller.extend({
return options;
},

@computed("site.groups")
siteGroups(groups) {
const values = [{ name: "", value: null }];
groups.forEach(g => values.push({ name: g.name, value: g.name }));
return values;
},

@computed("pollType", "regularPollType")
isRegular(pollType, regularPollType) {
return pollType === regularPollType;
Expand Down Expand Up @@ -184,6 +191,7 @@ export default Controller.extend({
"pollMin",
"pollMax",
"pollStep",
"pollGroups",
"autoClose",
"chartType",
"date",
Expand All @@ -199,6 +207,7 @@ export default Controller.extend({
pollMin,
pollMax,
pollStep,
pollGroups,
autoClose,
chartType,
date,
Expand Down Expand Up @@ -228,6 +237,7 @@ export default Controller.extend({
if (publicPoll) pollHeader += ` public=true`;
if (chartType && pollType !== "number")
pollHeader += ` chartType=${chartType}`;
if (pollGroups) pollHeader += ` groups=${pollGroups}`;
if (autoClose) {
let closeDate = moment(
date + " " + time,
Expand Down Expand Up @@ -323,6 +333,7 @@ export default Controller.extend({
pollStep: 1,
autoClose: false,
chartType: BAR_CHART_TYPE,
pollGroups: null,
date: moment()
.add(1, "day")
.format("YYYY-MM-DD"),
Expand Down
Expand Up @@ -17,6 +17,14 @@
valueAttribute="value"}}
</div>

<div class="input-group poll-select">
<label class="input-group-label">{{i18n 'poll.ui_builder.poll_groups.label'}}</label>
{{combo-box content=siteGroups
value=pollGroups
allowInitialValueMutation=true
valueAttribute="value"}}
</div>

{{#unless isNumber}}
<div class="input-group poll-select">
<label class="input-group-label">{{i18n 'poll.ui_builder.poll_chart_type.label'}}</label>
Expand Down
Expand Up @@ -11,6 +11,7 @@ const WHITELISTED_ATTRIBUTES = [
"public",
"results",
"chartType",
"groups",
"status",
"step",
"type"
Expand Down
66 changes: 48 additions & 18 deletions plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6
Expand Up @@ -333,16 +333,43 @@ createWidget("discourse-poll-container", {
: "discourse-poll-pie-chart";
return this.attach(resultsWidget, attrs);
} else if (options) {
return h(
"ul",
options.map(option => {
return this.attach("discourse-poll-option", {
option,
isMultiple: attrs.isMultiple,
vote: attrs.vote
});
})
const contents = [];

const pollGroups =
poll.groups && poll.groups.split(",").map(g => g.toLowerCase());

const userGroups =
this.currentUser &&
this.currentUser.groups &&
this.currentUser.groups.map(g => g.name.toLowerCase());

if (
pollGroups &&
userGroups &&
!pollGroups.some(g => userGroups.includes(g))
) {
contents.push(
h(
"div.alert.alert-danger",
I18n.t("poll.results.groups.title", { groups: poll.groups })
)
);
}

contents.push(
h(
"ul",
options.map(option => {
return this.attach("discourse-poll-option", {
option,
isMultiple: attrs.isMultiple,
vote: attrs.vote
});
})
)
);

return contents;
}
}
});
Expand Down Expand Up @@ -954,27 +981,30 @@ export default createWidget("discourse-poll", {
this.register.lookup("route:application").send("showLogin");
},

_toggleOption(option) {
const { vote } = this.attrs;
const chosenIdx = vote.indexOf(option.id);
if (chosenIdx !== -1) {
vote.splice(chosenIdx, 1);
} else {
vote.push(option.id);
}
},

toggleOption(option) {
const { attrs } = this;

if (this.isClosed()) return;
if (!this.currentUser) return this.showLogin();

const { vote } = attrs;
const chosenIdx = vote.indexOf(option.id);

if (!this.isMultiple()) {
vote.length = 0;
}

if (chosenIdx !== -1) {
vote.splice(chosenIdx, 1);
} else {
vote.push(option.id);
}

this._toggleOption(option);
if (!this.isMultiple()) {
return this.castVotes();
return this.castVotes().catch(() => this._toggleOption(option));
}
},

Expand Down
4 changes: 4 additions & 0 deletions plugins/poll/config/locales/client.en.yml
Expand Up @@ -31,6 +31,8 @@ en:
title: "Votes are <strong>public</strong>."

results:
groups:
title: "You need to be a member of %{groups} to vote in this poll."
vote:
title: "Results will be shown on <strong>vote</strong>."
closed:
Expand Down Expand Up @@ -112,6 +114,8 @@ en:
vote: On vote
closed: When closed
staff: Staff only
poll_groups:
label: Allowed groups
poll_chart_type:
label: Chart type
poll_config:
Expand Down
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddGroupNameToPolls < ActiveRecord::Migration[5.2]
def change
add_column :polls, :groups, :string
end
end
3 changes: 2 additions & 1 deletion plugins/poll/lib/polls_updater.rb
Expand Up @@ -3,7 +3,7 @@
module DiscoursePoll
class PollsUpdater

POLL_ATTRIBUTES ||= %w{close_at max min results status step type visibility}
POLL_ATTRIBUTES ||= %w{close_at max min results status step type visibility groups}

def self.update(post, polls)
::Poll.transaction do
Expand Down Expand Up @@ -38,6 +38,7 @@ def self.update(post, polls)
attributes["visibility"] = new_poll["public"] == "true" ? "everyone" : "secret"
attributes["close_at"] = Time.zone.parse(new_poll["close"]) rescue nil
attributes["status"] = old_poll["status"]
attributes["groups"] = new_poll["groups"]
poll = ::Poll.new(attributes)

if is_different?(old_poll, poll, new_poll_options)
Expand Down
11 changes: 10 additions & 1 deletion plugins/poll/plugin.rb
Expand Up @@ -71,6 +71,14 @@ def vote(post_id, poll_name, options, user)
raise StandardError.new I18n.t("poll.no_poll_with_this_name", name: poll_name) unless poll
raise StandardError.new I18n.t("poll.poll_must_be_open_to_vote") if poll.is_closed?

if poll.groups
poll_groups = poll.groups.split(",").map(&:downcase)
user_groups = user.groups.map { |g| g.name.downcase }
if (poll_groups & user_groups).empty?
raise StandardError.new I18n.t("js.poll.results.groups.title", group: poll.groups)
end
end

# 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) }
Expand Down Expand Up @@ -322,7 +330,8 @@ def create!(post_id, poll)
min: poll["min"],
max: poll["max"],
step: poll["step"],
chart_type: poll["charttype"] || "bar"
chart_type: poll["charttype"] || "bar",
groups: poll["groups"]
)

poll["options"].each do |option|
Expand Down
12 changes: 12 additions & 0 deletions plugins/poll/spec/controllers/polls_controller_spec.rb
Expand Up @@ -186,6 +186,18 @@
expect(json["errors"][0]).to eq(I18n.t("poll.poll_must_be_open_to_vote"))
end

it "ensures user has required trust level" do
poll = create_post(raw: "[poll groups=#{Fabricate(:group).name}]\n- A\n- B\n[/poll]")

put :vote, params: {
post_id: poll.id, poll_name: "poll", options: ["5c24fc1df56d764b550ceae1b9319125"]
}, format: :json

expect(response.status).not_to eq(200)
json = ::JSON.parse(response.body)
expect(json["errors"][0]).to eq(I18n.t("js.poll.results.groups.title", trust_level: 2))
end

it "doesn't discard anonymous votes when someone votes" do
the_poll = poll.polls.first
the_poll.update_attribute(:anonymous_voters, 17)
Expand Down
Expand Up @@ -283,6 +283,14 @@ test("regular pollOutput", function(assert) {
"[poll type=regular public=true chartType=bar]\n* 1\n* 2\n[/poll]\n",
"it should return the right output"
);

controller.set("pollGroups", "test");

assert.equal(
controller.get("pollOutput"),
"[poll type=regular public=true chartType=bar groups=test]\n* 1\n* 2\n[/poll]\n",
"it should return the right output"
);
});

test("multiple pollOutput", function(assert) {
Expand Down

1 comment on commit 07222af

@discoursebot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit has been mentioned on Discourse Meta. There might be relevant details there:

https://meta.discourse.org/t/is-it-possible-to-see-who-voted-in-polls/27064/31

Please sign in to comment.