mirror of
https://github.com/discourse/discourse.git
synced 2025-03-06 11:19:51 +00: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
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# chart_type :integer default("bar"), not null
|
# chart_type :integer default("bar"), not null
|
||||||
|
# groups :string
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
|
@ -13,7 +13,8 @@ class PollSerializer < ApplicationSerializer
|
|||||||
:voters,
|
:voters,
|
||||||
:close,
|
:close,
|
||||||
:preloaded_voters,
|
:preloaded_voters,
|
||||||
:chart_type
|
:chart_type,
|
||||||
|
:groups
|
||||||
|
|
||||||
def public
|
def public
|
||||||
true
|
true
|
||||||
@ -35,6 +36,10 @@ class PollSerializer < ApplicationSerializer
|
|||||||
object.step.present? && object.number?
|
object.step.present? && object.number?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def include_groups?
|
||||||
|
groups.present?
|
||||||
|
end
|
||||||
|
|
||||||
def options
|
def options
|
||||||
object.poll_options.map { |o| PollOptionSerializer.new(o, root: false).as_json }
|
object.poll_options.map { |o| PollOptionSerializer.new(o, root: false).as_json }
|
||||||
end
|
end
|
||||||
|
@ -82,6 +82,13 @@ export default Controller.extend({
|
|||||||
return options;
|
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")
|
@computed("pollType", "regularPollType")
|
||||||
isRegular(pollType, regularPollType) {
|
isRegular(pollType, regularPollType) {
|
||||||
return pollType === regularPollType;
|
return pollType === regularPollType;
|
||||||
@ -184,6 +191,7 @@ export default Controller.extend({
|
|||||||
"pollMin",
|
"pollMin",
|
||||||
"pollMax",
|
"pollMax",
|
||||||
"pollStep",
|
"pollStep",
|
||||||
|
"pollGroups",
|
||||||
"autoClose",
|
"autoClose",
|
||||||
"chartType",
|
"chartType",
|
||||||
"date",
|
"date",
|
||||||
@ -199,6 +207,7 @@ export default Controller.extend({
|
|||||||
pollMin,
|
pollMin,
|
||||||
pollMax,
|
pollMax,
|
||||||
pollStep,
|
pollStep,
|
||||||
|
pollGroups,
|
||||||
autoClose,
|
autoClose,
|
||||||
chartType,
|
chartType,
|
||||||
date,
|
date,
|
||||||
@ -228,6 +237,7 @@ export default Controller.extend({
|
|||||||
if (publicPoll) pollHeader += ` public=true`;
|
if (publicPoll) pollHeader += ` public=true`;
|
||||||
if (chartType && pollType !== "number")
|
if (chartType && pollType !== "number")
|
||||||
pollHeader += ` chartType=${chartType}`;
|
pollHeader += ` chartType=${chartType}`;
|
||||||
|
if (pollGroups) pollHeader += ` groups=${pollGroups}`;
|
||||||
if (autoClose) {
|
if (autoClose) {
|
||||||
let closeDate = moment(
|
let closeDate = moment(
|
||||||
date + " " + time,
|
date + " " + time,
|
||||||
@ -323,6 +333,7 @@ export default Controller.extend({
|
|||||||
pollStep: 1,
|
pollStep: 1,
|
||||||
autoClose: false,
|
autoClose: false,
|
||||||
chartType: BAR_CHART_TYPE,
|
chartType: BAR_CHART_TYPE,
|
||||||
|
pollGroups: null,
|
||||||
date: moment()
|
date: moment()
|
||||||
.add(1, "day")
|
.add(1, "day")
|
||||||
.format("YYYY-MM-DD"),
|
.format("YYYY-MM-DD"),
|
||||||
|
@ -17,6 +17,14 @@
|
|||||||
valueAttribute="value"}}
|
valueAttribute="value"}}
|
||||||
</div>
|
</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}}
|
{{#unless isNumber}}
|
||||||
<div class="input-group poll-select">
|
<div class="input-group poll-select">
|
||||||
<label class="input-group-label">{{i18n 'poll.ui_builder.poll_chart_type.label'}}</label>
|
<label class="input-group-label">{{i18n 'poll.ui_builder.poll_chart_type.label'}}</label>
|
||||||
|
@ -11,6 +11,7 @@ const WHITELISTED_ATTRIBUTES = [
|
|||||||
"public",
|
"public",
|
||||||
"results",
|
"results",
|
||||||
"chartType",
|
"chartType",
|
||||||
|
"groups",
|
||||||
"status",
|
"status",
|
||||||
"step",
|
"step",
|
||||||
"type"
|
"type"
|
||||||
|
@ -333,16 +333,43 @@ createWidget("discourse-poll-container", {
|
|||||||
: "discourse-poll-pie-chart";
|
: "discourse-poll-pie-chart";
|
||||||
return this.attach(resultsWidget, attrs);
|
return this.attach(resultsWidget, attrs);
|
||||||
} else if (options) {
|
} else if (options) {
|
||||||
return h(
|
const contents = [];
|
||||||
"ul",
|
|
||||||
options.map(option => {
|
const pollGroups =
|
||||||
return this.attach("discourse-poll-option", {
|
poll.groups && poll.groups.split(",").map(g => g.toLowerCase());
|
||||||
option,
|
|
||||||
isMultiple: attrs.isMultiple,
|
const userGroups =
|
||||||
vote: attrs.vote
|
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");
|
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) {
|
toggleOption(option) {
|
||||||
const { attrs } = this;
|
const { attrs } = this;
|
||||||
|
|
||||||
@ -961,20 +998,13 @@ export default createWidget("discourse-poll", {
|
|||||||
if (!this.currentUser) return this.showLogin();
|
if (!this.currentUser) return this.showLogin();
|
||||||
|
|
||||||
const { vote } = attrs;
|
const { vote } = attrs;
|
||||||
const chosenIdx = vote.indexOf(option.id);
|
|
||||||
|
|
||||||
if (!this.isMultiple()) {
|
if (!this.isMultiple()) {
|
||||||
vote.length = 0;
|
vote.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chosenIdx !== -1) {
|
this._toggleOption(option);
|
||||||
vote.splice(chosenIdx, 1);
|
|
||||||
} else {
|
|
||||||
vote.push(option.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.isMultiple()) {
|
if (!this.isMultiple()) {
|
||||||
return this.castVotes();
|
return this.castVotes().catch(() => this._toggleOption(option));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -31,6 +31,8 @@ en:
|
|||||||
title: "Votes are <strong>public</strong>."
|
title: "Votes are <strong>public</strong>."
|
||||||
|
|
||||||
results:
|
results:
|
||||||
|
groups:
|
||||||
|
title: "You need to be a member of %{groups} to vote in this poll."
|
||||||
vote:
|
vote:
|
||||||
title: "Results will be shown on <strong>vote</strong>."
|
title: "Results will be shown on <strong>vote</strong>."
|
||||||
closed:
|
closed:
|
||||||
@ -112,6 +114,8 @@ en:
|
|||||||
vote: On vote
|
vote: On vote
|
||||||
closed: When closed
|
closed: When closed
|
||||||
staff: Staff only
|
staff: Staff only
|
||||||
|
poll_groups:
|
||||||
|
label: Allowed groups
|
||||||
poll_chart_type:
|
poll_chart_type:
|
||||||
label: Chart type
|
label: Chart type
|
||||||
poll_config:
|
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
|
module DiscoursePoll
|
||||||
class PollsUpdater
|
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)
|
def self.update(post, polls)
|
||||||
::Poll.transaction do
|
::Poll.transaction do
|
||||||
@ -38,6 +38,7 @@ module DiscoursePoll
|
|||||||
attributes["visibility"] = new_poll["public"] == "true" ? "everyone" : "secret"
|
attributes["visibility"] = new_poll["public"] == "true" ? "everyone" : "secret"
|
||||||
attributes["close_at"] = Time.zone.parse(new_poll["close"]) rescue nil
|
attributes["close_at"] = Time.zone.parse(new_poll["close"]) rescue nil
|
||||||
attributes["status"] = old_poll["status"]
|
attributes["status"] = old_poll["status"]
|
||||||
|
attributes["groups"] = new_poll["groups"]
|
||||||
poll = ::Poll.new(attributes)
|
poll = ::Poll.new(attributes)
|
||||||
|
|
||||||
if is_different?(old_poll, poll, new_poll_options)
|
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.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?
|
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
|
# remove options that aren't available in the poll
|
||||||
available_options = poll.poll_options.map { |o| o.digest }.to_set
|
available_options = poll.poll_options.map { |o| o.digest }.to_set
|
||||||
options.select! { |o| available_options.include?(o) }
|
options.select! { |o| available_options.include?(o) }
|
||||||
@ -322,7 +330,8 @@ after_initialize do
|
|||||||
min: poll["min"],
|
min: poll["min"],
|
||||||
max: poll["max"],
|
max: poll["max"],
|
||||||
step: poll["step"],
|
step: poll["step"],
|
||||||
chart_type: poll["charttype"] || "bar"
|
chart_type: poll["charttype"] || "bar",
|
||||||
|
groups: poll["groups"]
|
||||||
)
|
)
|
||||||
|
|
||||||
poll["options"].each do |option|
|
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"))
|
expect(json["errors"][0]).to eq(I18n.t("poll.poll_must_be_open_to_vote"))
|
||||||
end
|
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
|
it "doesn't discard anonymous votes when someone votes" do
|
||||||
the_poll = poll.polls.first
|
the_poll = poll.polls.first
|
||||||
the_poll.update_attribute(:anonymous_voters, 17)
|
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",
|
"[poll type=regular public=true chartType=bar]\n* 1\n* 2\n[/poll]\n",
|
||||||
"it should return the right output"
|
"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) {
|
test("multiple pollOutput", function(assert) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user