mirror of
https://github.com/discourse/discourse.git
synced 2024-11-26 18:03:38 +08:00
FEATURE: Add 'groups' option to polls (#8469)
This options can be used to restrict polls to certain groups.
This commit is contained in:
parent
a9d0d55817
commit
07222af7ab
|
@ -78,6 +78,7 @@ end
|
|||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# chart_type :integer default("bar"), not null
|
||||
# groups :string
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
|
|
|
@ -13,7 +13,8 @@ class PollSerializer < ApplicationSerializer
|
|||
:voters,
|
||||
:close,
|
||||
:preloaded_voters,
|
||||
:chart_type
|
||||
:chart_type,
|
||||
:groups
|
||||
|
||||
def public
|
||||
true
|
||||
|
@ -35,6 +36,10 @@ class PollSerializer < ApplicationSerializer
|
|||
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
|
||||
|
|
|
@ -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;
|
||||
|
@ -184,6 +191,7 @@ export default Controller.extend({
|
|||
"pollMin",
|
||||
"pollMax",
|
||||
"pollStep",
|
||||
"pollGroups",
|
||||
"autoClose",
|
||||
"chartType",
|
||||
"date",
|
||||
|
@ -199,6 +207,7 @@ export default Controller.extend({
|
|||
pollMin,
|
||||
pollMax,
|
||||
pollStep,
|
||||
pollGroups,
|
||||
autoClose,
|
||||
chartType,
|
||||
date,
|
||||
|
@ -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,
|
||||
|
@ -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"),
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -11,6 +11,7 @@ const WHITELISTED_ATTRIBUTES = [
|
|||
"public",
|
||||
"results",
|
||||
"chartType",
|
||||
"groups",
|
||||
"status",
|
||||
"step",
|
||||
"type"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -954,6 +981,16 @@ 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;
|
||||
|
||||
|
@ -961,20 +998,13 @@ export default createWidget("discourse-poll", {
|
|||
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));
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
@ -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:
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddGroupNameToPolls < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :polls, :groups, :string
|
||||
end
|
||||
end
|
|
@ -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
|
||||
|
@ -38,6 +38,7 @@ module DiscoursePoll
|
|||
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)
|
||||
|
|
|
@ -71,6 +71,14 @@ after_initialize do
|
|||
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) }
|
||||
|
@ -322,7 +330,8 @@ after_initialize do
|
|||
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|
|
||||
|
|
|
@ -186,6 +186,18 @@ describe ::DiscoursePoll::PollsController do
|
|||
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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user