Feature: Allow assign on groups (#31)
* Feature: Allow assign on groups * Use type: group_list option * Track group changes and update the setting accordingly. Restrict reminders frequency to assign allowed users instead of staff
This commit is contained in:
parent
8536795da3
commit
f312ece4a9
|
@ -1,13 +1,13 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAssign
|
module DiscourseAssign
|
||||||
class AssignController < Admin::AdminController
|
class AssignController < ApplicationController
|
||||||
before_action :ensure_logged_in
|
requires_login
|
||||||
|
before_action :ensure_logged_in, :ensure_assign_allowed
|
||||||
|
|
||||||
def suggestions
|
def suggestions
|
||||||
users = [current_user]
|
users = [current_user]
|
||||||
users += User
|
users += User
|
||||||
.where('admin OR moderator')
|
|
||||||
.where('users.id <> ?', current_user.id)
|
.where('users.id <> ?', current_user.id)
|
||||||
.joins(<<~SQL
|
.joins(<<~SQL
|
||||||
JOIN(
|
JOIN(
|
||||||
|
@ -18,7 +18,9 @@ module DiscourseAssign
|
||||||
HAVING COUNT(*) < #{SiteSetting.max_assigned_topics}
|
HAVING COUNT(*) < #{SiteSetting.max_assigned_topics}
|
||||||
) as X ON X.user_id = users.id
|
) as X ON X.user_id = users.id
|
||||||
SQL
|
SQL
|
||||||
).order('X.last_assigned DESC')
|
)
|
||||||
|
.assign_allowed
|
||||||
|
.order('X.last_assigned DESC')
|
||||||
.limit(6)
|
.limit(6)
|
||||||
|
|
||||||
render json: ActiveModel::ArraySerializer.new(users,
|
render json: ActiveModel::ArraySerializer.new(users,
|
||||||
|
@ -88,5 +90,9 @@ module DiscourseAssign
|
||||||
{ error: I18n.t('discourse_assign.too_many_assigns', username: user.username, max: max) }
|
{ error: I18n.t('discourse_assign.too_many_assigns', username: user.username, max: max) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ensure_assign_allowed
|
||||||
|
raise Discourse::InvalidAccess.new unless current_user.can_assign?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { getOwner } from "discourse-common/lib/get-owner";
|
||||||
export default {
|
export default {
|
||||||
shouldRender(args, component) {
|
shouldRender(args, component) {
|
||||||
const needsButton =
|
const needsButton =
|
||||||
component.currentUser && component.currentUser.get("staff");
|
component.currentUser && component.currentUser.get("can_assign");
|
||||||
return (
|
return (
|
||||||
needsButton &&
|
needsButton &&
|
||||||
(!component.get("site.mobileView") || args.topic.get("isPrivateMessage"))
|
(!component.get("site.mobileView") || args.topic.get("isPrivateMessage"))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{{#if currentUser.staff}}
|
{{#if currentUser.can_assign}}
|
||||||
{{#link-to 'userActivity.assigned'}}
|
{{#link-to 'userActivity.assigned'}}
|
||||||
{{d-icon "user-plus"}} {{i18n 'discourse_assign.assigned'}}
|
{{d-icon "user-plus"}} {{i18n 'discourse_assign.assigned'}}
|
||||||
{{/link-to}}
|
{{/link-to}}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export function shouldShowAssigned(args, component) {
|
export function shouldShowAssigned(args, component) {
|
||||||
const needsButton =
|
const needsButton =
|
||||||
component.currentUser && component.currentUser.get("staff");
|
component.currentUser && component.currentUser.get("can_assign");
|
||||||
return (
|
return (
|
||||||
needsButton &&
|
needsButton &&
|
||||||
(!component.get("site.mobileView") || args.model.get("isPrivateMessage"))
|
(!component.get("site.mobileView") || args.model.get("isPrivateMessage"))
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export default {
|
export default {
|
||||||
shouldRender(args, component) {
|
shouldRender(args, component) {
|
||||||
return component.currentUser && component.currentUser.get("staff");
|
return component.currentUser && component.currentUser.get("can_assign");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { ajax } from "discourse/lib/ajax";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
|
||||||
export default Ember.Controller.extend({
|
export default Ember.Controller.extend({
|
||||||
|
taskActions: Ember.inject.service(),
|
||||||
assignSuggestions: function() {
|
assignSuggestions: function() {
|
||||||
ajax("/assign/suggestions").then(users => {
|
ajax("/assign/suggestions").then(users => {
|
||||||
this.set("assignSuggestions", users);
|
this.set("assignSuggestions", users);
|
||||||
|
@ -23,6 +24,7 @@ export default Ember.Controller.extend({
|
||||||
actions: {
|
actions: {
|
||||||
assignUser(user) {
|
assignUser(user) {
|
||||||
this.set("model.username", user.username);
|
this.set("model.username", user.username);
|
||||||
|
this.set("model.allowedGroups", this.get("taskActions").allowedGroups);
|
||||||
this.send("assign");
|
this.send("assign");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ function modifySelectKit(api) {
|
||||||
api
|
api
|
||||||
.modifySelectKit("topic-footer-mobile-dropdown")
|
.modifySelectKit("topic-footer-mobile-dropdown")
|
||||||
.modifyContent((context, existingContent) => {
|
.modifyContent((context, existingContent) => {
|
||||||
if (context.get("currentUser.staff")) {
|
if (context.get("currentUser.can_assign")) {
|
||||||
const hasAssignement = context.get("topic.assigned_to_user");
|
const hasAssignement = context.get("topic.assigned_to_user");
|
||||||
const button = {
|
const button = {
|
||||||
id: ACTION_ID,
|
id: ACTION_ID,
|
||||||
|
@ -31,7 +31,7 @@ function modifySelectKit(api) {
|
||||||
return existingContent;
|
return existingContent;
|
||||||
})
|
})
|
||||||
.onSelect((context, value) => {
|
.onSelect((context, value) => {
|
||||||
if (!context.get("currentUser.staff") || value !== ACTION_ID) {
|
if (!context.get("currentUser.can_assign") || value !== ACTION_ID) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ function initialize(api) {
|
||||||
});
|
});
|
||||||
|
|
||||||
api.addUserMenuGlyph(widget => {
|
api.addUserMenuGlyph(widget => {
|
||||||
if (widget.currentUser && widget.currentUser.get("staff")) {
|
if (widget.currentUser && widget.currentUser.get("can_assign")) {
|
||||||
return {
|
return {
|
||||||
label: "discourse_assign.assigned",
|
label: "discourse_assign.assigned",
|
||||||
className: "assigned",
|
className: "assigned",
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import showModal from "discourse/lib/show-modal";
|
import showModal from "discourse/lib/show-modal";
|
||||||
|
import { getOwner } from "discourse-common/lib/get-owner";
|
||||||
|
|
||||||
export default Ember.Service.extend({
|
export default Ember.Service.extend({
|
||||||
|
init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
this.allowedGroups = getOwner(this)
|
||||||
|
.lookup("site-settings:main")
|
||||||
|
.assign_allowed_on_groups.split("|");
|
||||||
|
},
|
||||||
|
|
||||||
unassign(topicId) {
|
unassign(topicId) {
|
||||||
return ajax("/assign/unassign", {
|
return ajax("/assign/unassign", {
|
||||||
type: "PUT",
|
type: "PUT",
|
||||||
|
@ -13,7 +22,8 @@ export default Ember.Service.extend({
|
||||||
return showModal("assign-user", {
|
return showModal("assign-user", {
|
||||||
model: {
|
model: {
|
||||||
topic: topic,
|
topic: topic,
|
||||||
username: topic.get("assigned_to_user.username")
|
username: topic.get("assigned_to_user.username"),
|
||||||
|
allowedGroups: this.allowedGroups
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
single=true
|
single=true
|
||||||
allowAny=false
|
allowAny=false
|
||||||
group="staff"
|
group="staff"
|
||||||
|
groupMembersOf=model.allowedGroups
|
||||||
excludeCurrentUser=false
|
excludeCurrentUser=false
|
||||||
includeMentionableGroups=false
|
includeMentionableGroups=false
|
||||||
hasGroups=false
|
hasGroups=false
|
||||||
|
|
|
@ -15,6 +15,7 @@ en:
|
||||||
remind_assigns: "Remind users about pending assigns."
|
remind_assigns: "Remind users about pending assigns."
|
||||||
remind_assigns_frequency: "Frequency for reminding users about assigned topics."
|
remind_assigns_frequency: "Frequency for reminding users about assigned topics."
|
||||||
max_assigned_topics: "Maximum number of topics that can be assigned to a user."
|
max_assigned_topics: "Maximum number of topics that can be assigned to a user."
|
||||||
|
assign_allowed_on_groups: "Groups that are allowed to assign topics."
|
||||||
discourse_assign:
|
discourse_assign:
|
||||||
assigned_to: "Topic assigned to @%{username}"
|
assigned_to: "Topic assigned to @%{username}"
|
||||||
unassigned: "Topic was unassigned"
|
unassigned: "Topic was unassigned"
|
||||||
|
|
|
@ -27,3 +27,10 @@ plugins:
|
||||||
client: true
|
client: true
|
||||||
default: 10
|
default: 10
|
||||||
min: 1
|
min: 1
|
||||||
|
assign_allowed_on_groups:
|
||||||
|
client: true
|
||||||
|
type: group_list
|
||||||
|
list_type: compact
|
||||||
|
default: 'staff'
|
||||||
|
allow_any: false
|
||||||
|
refresh: true
|
||||||
|
|
|
@ -15,6 +15,11 @@ module Jobs
|
||||||
SiteSetting.remind_assigns_frequency.nil? || !SiteSetting.assign_enabled?
|
SiteSetting.remind_assigns_frequency.nil? || !SiteSetting.assign_enabled?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def allowed_group_ids
|
||||||
|
allowed_groups = SiteSetting.assign_allowed_on_groups.split('|')
|
||||||
|
Group.where(name: allowed_groups).pluck(:id).join(',')
|
||||||
|
end
|
||||||
|
|
||||||
def user_ids
|
def user_ids
|
||||||
global_frequency = SiteSetting.remind_assigns_frequency
|
global_frequency = SiteSetting.remind_assigns_frequency
|
||||||
frequency = ActiveRecord::Base.sanitize_sql("COALESCE(user_frequency.value, '#{global_frequency}')::INT")
|
frequency = ActiveRecord::Base.sanitize_sql("COALESCE(user_frequency.value, '#{global_frequency}')::INT")
|
||||||
|
@ -31,10 +36,10 @@ module Jobs
|
||||||
ON topic_custom_fields.value::INT = user_frequency.user_id
|
ON topic_custom_fields.value::INT = user_frequency.user_id
|
||||||
AND user_frequency.name = '#{PendingAssignsReminder::REMINDERS_FREQUENCY}'
|
AND user_frequency.name = '#{PendingAssignsReminder::REMINDERS_FREQUENCY}'
|
||||||
|
|
||||||
INNER JOIN users ON topic_custom_fields.value::INT = users.id
|
INNER JOIN group_users ON topic_custom_fields.value::INT = group_users.user_id
|
||||||
INNER JOIN topics ON topics.id = topic_custom_fields.topic_id AND (topics.deleted_at IS NULL)
|
INNER JOIN topics ON topics.id = topic_custom_fields.topic_id AND (topics.deleted_at IS NULL)
|
||||||
|
|
||||||
WHERE (users.moderator OR users.admin)
|
WHERE group_users.group_id IN (#{allowed_group_ids})
|
||||||
AND #{frequency} > 0
|
AND #{frequency} > 0
|
||||||
AND (
|
AND (
|
||||||
last_reminder.value IS NULL OR
|
last_reminder.value IS NULL OR
|
||||||
|
|
|
@ -8,12 +8,12 @@ class ::TopicAssigner
|
||||||
ASSIGNED_BY_ID = 'assigned_by_id'
|
ASSIGNED_BY_ID = 'assigned_by_id'
|
||||||
|
|
||||||
def self.backfill_auto_assign
|
def self.backfill_auto_assign
|
||||||
staff_mention = User.where('moderator OR admin')
|
staff_mention = User.assign_allowed
|
||||||
.pluck('username')
|
.pluck('username')
|
||||||
.map { |name| "p.cooked ILIKE '%mention%@#{name}%'" }
|
.map! { |name| "p.cooked ILIKE '%mention%@#{name}%'" }
|
||||||
.join(' OR ')
|
.join(' OR ')
|
||||||
|
|
||||||
sql = <<SQL
|
sql = <<~SQL
|
||||||
SELECT p.topic_id, MAX(post_number) post_number
|
SELECT p.topic_id, MAX(post_number) post_number
|
||||||
FROM posts p
|
FROM posts p
|
||||||
JOIN topics t ON t.id = p.topic_id
|
JOIN topics t ON t.id = p.topic_id
|
||||||
|
@ -54,7 +54,7 @@ SQL
|
||||||
def self.auto_assign(post, force: false)
|
def self.auto_assign(post, force: false)
|
||||||
return unless SiteSetting.assigns_by_staff_mention
|
return unless SiteSetting.assigns_by_staff_mention
|
||||||
|
|
||||||
if post.user && post.topic && post.user.staff?
|
if post.user && post.topic && post.user.can_assign?
|
||||||
can_assign = force || post.topic.custom_fields[ASSIGNED_TO_ID].nil?
|
can_assign = force || post.topic.custom_fields[ASSIGNED_TO_ID].nil?
|
||||||
|
|
||||||
assign_other = assign_other_passes?(post) && mentioned_staff(post)
|
assign_other = assign_other_passes?(post) && mentioned_staff(post)
|
||||||
|
@ -72,12 +72,14 @@ SQL
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.is_last_staff_post?(post)
|
def self.is_last_staff_post?(post)
|
||||||
|
allowed_user_ids = User.assign_allowed.pluck(:id).join(',')
|
||||||
|
|
||||||
sql = <<~SQL
|
sql = <<~SQL
|
||||||
SELECT 1 FROM posts p
|
SELECT 1 FROM posts p
|
||||||
JOIN users u ON u.id = p.user_id AND (moderator OR admin)
|
JOIN users u ON u.id = p.user_id
|
||||||
WHERE p.deleted_at IS NULL AND p.topic_id = :topic_id
|
WHERE p.deleted_at IS NULL AND p.topic_id = :topic_id
|
||||||
|
AND u.id IN (#{allowed_user_ids})
|
||||||
having max(post_number) = :post_number
|
having max(post_number) = :post_number
|
||||||
|
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
|
@ -85,19 +87,16 @@ SQL
|
||||||
post_number: post.post_number
|
post_number: post.post_number
|
||||||
}
|
}
|
||||||
|
|
||||||
# TODO post 2.1 release remove
|
|
||||||
if defined?(DB)
|
|
||||||
DB.exec(sql, args) == 1
|
DB.exec(sql, args) == 1
|
||||||
else
|
|
||||||
Post.exec_sql(sql, args).to_a.length == 1
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.mentioned_staff(post)
|
def self.mentioned_staff(post)
|
||||||
mentions = post.raw_mentions
|
mentions = post.raw_mentions
|
||||||
if mentions.present?
|
if mentions.present?
|
||||||
User.where('moderator OR admin')
|
allowed_groups = SiteSetting.assign_allowed_on_groups.split('|')
|
||||||
.human_users
|
|
||||||
|
User.human_users
|
||||||
|
.joins(:groups).where(groups: { name: allowed_groups })
|
||||||
.where('username_lower IN (?)', mentions.map(&:downcase))
|
.where('username_lower IN (?)', mentions.map(&:downcase))
|
||||||
.first
|
.first
|
||||||
end
|
end
|
||||||
|
@ -108,8 +107,8 @@ SQL
|
||||||
@topic = topic
|
@topic = topic
|
||||||
end
|
end
|
||||||
|
|
||||||
def staff_ids
|
def allowed_user_ids
|
||||||
User.real.staff.pluck(:id)
|
User.assign_allowed.pluck(:id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_assign_to?(user)
|
def can_assign_to?(user)
|
||||||
|
@ -143,7 +142,7 @@ SQL
|
||||||
topic_id: @topic.id,
|
topic_id: @topic.id,
|
||||||
assigned_to: BasicUserSerializer.new(assign_to, root: false).as_json
|
assigned_to: BasicUserSerializer.new(assign_to, root: false).as_json
|
||||||
},
|
},
|
||||||
user_ids: staff_ids
|
user_ids: allowed_user_ids
|
||||||
)
|
)
|
||||||
|
|
||||||
publish_topic_tracking_state(@topic, assign_to.id)
|
publish_topic_tracking_state(@topic, assign_to.id)
|
||||||
|
@ -274,7 +273,7 @@ SQL
|
||||||
type: 'unassigned',
|
type: 'unassigned',
|
||||||
topic_id: @topic.id,
|
topic_id: @topic.id,
|
||||||
},
|
},
|
||||||
user_ids: staff_ids
|
user_ids: allowed_user_ids
|
||||||
)
|
)
|
||||||
|
|
||||||
publish_topic_tracking_state(@topic, assigned_user.id)
|
publish_topic_tracking_state(@topic, assigned_user.id)
|
||||||
|
|
63
plugin.rb
63
plugin.rb
|
@ -48,6 +48,34 @@ after_initialize do
|
||||||
=end
|
=end
|
||||||
reviewable_api_enabled = defined?(Reviewable)
|
reviewable_api_enabled = defined?(Reviewable)
|
||||||
|
|
||||||
|
add_to_class(:user, :can_assign?) do
|
||||||
|
allowed_groups = SiteSetting.assign_allowed_on_groups.split('|')
|
||||||
|
(groups.pluck(:name) & allowed_groups).present?
|
||||||
|
end
|
||||||
|
|
||||||
|
add_to_class(:guardian, :can_assign?) { user && user.can_assign? }
|
||||||
|
|
||||||
|
add_class_method(:user, :assign_allowed) do
|
||||||
|
allowed_groups = SiteSetting.assign_allowed_on_groups.split('|')
|
||||||
|
where('users.id IN (
|
||||||
|
SELECT user_id FROM group_users
|
||||||
|
INNER JOIN groups ON group_users.group_id = groups.id
|
||||||
|
WHERE groups.name IN (?)
|
||||||
|
)', allowed_groups)
|
||||||
|
end
|
||||||
|
|
||||||
|
add_model_callback(Group, :before_update) do
|
||||||
|
if name_changed?
|
||||||
|
SiteSetting.assign_allowed_on_groups = SiteSetting.assign_allowed_on_groups.gsub(name_was, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
add_model_callback(Group, :before_destroy) do
|
||||||
|
new_setting = SiteSetting.assign_allowed_on_groups.gsub(/#{name}[|]?/, '')
|
||||||
|
new_setting = new_setting.chomp('|') if new_setting.ends_with?('|')
|
||||||
|
SiteSetting.assign_allowed_on_groups = new_setting
|
||||||
|
end
|
||||||
|
|
||||||
# Raise an invalid access error if a user tries to act on something
|
# Raise an invalid access error if a user tries to act on something
|
||||||
# not assigned to them
|
# not assigned to them
|
||||||
DiscourseEvent.on(:before_staff_flag_action) do |args|
|
DiscourseEvent.on(:before_staff_flag_action) do |args|
|
||||||
|
@ -88,8 +116,8 @@ after_initialize do
|
||||||
|
|
||||||
TopicList.on_preload do |topics, topic_list|
|
TopicList.on_preload do |topics, topic_list|
|
||||||
if SiteSetting.assign_enabled?
|
if SiteSetting.assign_enabled?
|
||||||
is_staff = topic_list.current_user && topic_list.current_user.staff?
|
can_assign = topic_list.current_user && topic_list.current_user.can_assign?
|
||||||
allowed_access = SiteSetting.assigns_public || is_staff
|
allowed_access = SiteSetting.assigns_public || can_assign
|
||||||
|
|
||||||
if allowed_access && topics.length > 0
|
if allowed_access && topics.length > 0
|
||||||
users = User.where("users.id in (
|
users = User.where("users.id in (
|
||||||
|
@ -114,7 +142,7 @@ after_initialize do
|
||||||
|
|
||||||
require_dependency 'topic_query'
|
require_dependency 'topic_query'
|
||||||
TopicQuery.add_custom_filter(:assigned) do |results, topic_query|
|
TopicQuery.add_custom_filter(:assigned) do |results, topic_query|
|
||||||
if topic_query.guardian.is_staff? || SiteSetting.assigns_public
|
if topic_query.guardian.can_assign? || SiteSetting.assigns_public
|
||||||
username = topic_query.options[:assigned]
|
username = topic_query.options[:assigned]
|
||||||
|
|
||||||
user_id = topic_query.guardian.user.id if username == "me"
|
user_id = topic_query.guardian.user.id if username == "me"
|
||||||
|
@ -202,10 +230,6 @@ after_initialize do
|
||||||
@assigned_to_user = assigned_to_user
|
@assigned_to_user = assigned_to_user
|
||||||
end
|
end
|
||||||
|
|
||||||
add_to_serializer(:topic_list_item, 'include_assigned_to_user?') do
|
|
||||||
(SiteSetting.assigns_public || scope.is_staff?) && object.assigned_to_user
|
|
||||||
end
|
|
||||||
|
|
||||||
add_to_serializer(:topic_list, :assigned_messages_count) do
|
add_to_serializer(:topic_list, :assigned_messages_count) do
|
||||||
TopicQuery.new(object.current_user, guardian: scope, limit: false)
|
TopicQuery.new(object.current_user, guardian: scope, limit: false)
|
||||||
.private_messages_assigned_query(object.current_user)
|
.private_messages_assigned_query(object.current_user)
|
||||||
|
@ -216,7 +240,7 @@ after_initialize do
|
||||||
options = object.instance_variable_get(:@opts)
|
options = object.instance_variable_get(:@opts)
|
||||||
|
|
||||||
if assigned_user = options.dig(:assigned)
|
if assigned_user = options.dig(:assigned)
|
||||||
scope.is_staff? ||
|
scope.can_assign? ||
|
||||||
assigned_user.downcase == scope.current_user&.username_lower
|
assigned_user.downcase == scope.current_user&.username_lower
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -225,19 +249,27 @@ after_initialize do
|
||||||
DiscourseAssign::Helpers.build_assigned_to_user(assigned_to_user_id, object.topic)
|
DiscourseAssign::Helpers.build_assigned_to_user(assigned_to_user_id, object.topic)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
add_to_serializer(:topic_list_item, 'include_assigned_to_user?') do
|
||||||
|
(SiteSetting.assigns_public || scope.can_assign?) && object.assigned_to_user
|
||||||
|
end
|
||||||
|
|
||||||
|
add_to_serializer(:topic_view, 'include_assigned_to_user?') do
|
||||||
|
if SiteSetting.assigns_public || scope.can_assign?
|
||||||
|
# subtle but need to catch cases where stuff is not assigned
|
||||||
|
object.topic.custom_fields.keys.include?(TopicAssigner::ASSIGNED_TO_ID)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
add_to_serializer(:current_user, :can_assign) do
|
||||||
|
object.can_assign?
|
||||||
|
end
|
||||||
|
|
||||||
add_to_class(:topic_view_serializer, :assigned_to_user_id) do
|
add_to_class(:topic_view_serializer, :assigned_to_user_id) do
|
||||||
id = object.topic.custom_fields[TopicAssigner::ASSIGNED_TO_ID]
|
id = object.topic.custom_fields[TopicAssigner::ASSIGNED_TO_ID]
|
||||||
# a bit messy but race conditions can give us an array here, avoid
|
# a bit messy but race conditions can give us an array here, avoid
|
||||||
id && id.to_i rescue nil
|
id && id.to_i rescue nil
|
||||||
end
|
end
|
||||||
|
|
||||||
add_to_serializer(:topic_view, 'include_assigned_to_user?') do
|
|
||||||
if SiteSetting.assigns_public || scope.is_staff?
|
|
||||||
# subtle but need to catch cases where stuff is not assigned
|
|
||||||
object.topic.custom_fields.keys.include?(TopicAssigner::ASSIGNED_TO_ID)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
add_to_serializer(:flagged_topic, :assigned_to_user) do
|
add_to_serializer(:flagged_topic, :assigned_to_user) do
|
||||||
DiscourseAssign::Helpers.build_assigned_to_user(assigned_to_user_id, object)
|
DiscourseAssign::Helpers.build_assigned_to_user(assigned_to_user_id, object)
|
||||||
end
|
end
|
||||||
|
@ -318,5 +350,4 @@ after_initialize do
|
||||||
assigner.unassign(silent: true)
|
assigner.unassign(silent: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe Jobs::EnqueueReminders do
|
RSpec.describe Jobs::EnqueueReminders do
|
||||||
let(:user) { Fabricate(:user, admin: true) }
|
let(:assign_allowed_group) { Group.find_by(name: 'staff') }
|
||||||
|
let(:user) { Fabricate(:user, groups: [assign_allowed_group]) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
SiteSetting.remind_assigns_frequency = RemindAssignsFrequencySiteSettings::MONTHLY_MINUTES
|
SiteSetting.remind_assigns_frequency = RemindAssignsFrequencySiteSettings::MONTHLY_MINUTES
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe PendingAssignsReminder do
|
RSpec.describe PendingAssignsReminder do
|
||||||
|
before { SiteSetting.assign_enabled = true }
|
||||||
|
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
it 'does not create a reminder if the user has 0 assigned topics' do
|
it 'does not create a reminder if the user has 0 assigned topics' do
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe TopicAssigner do
|
RSpec.describe TopicAssigner do
|
||||||
|
before { SiteSetting.assign_enabled = true }
|
||||||
|
|
||||||
|
let(:assign_allowed_group) { Group.find_by(name: 'staff') }
|
||||||
let(:pm_post) { Fabricate(:private_message_post) }
|
let(:pm_post) { Fabricate(:private_message_post) }
|
||||||
let(:pm) { pm_post.topic }
|
let(:pm) { pm_post.topic }
|
||||||
|
|
||||||
|
@ -18,6 +21,7 @@ RSpec.describe TopicAssigner do
|
||||||
describe 'assigning and unassigning private message' do
|
describe 'assigning and unassigning private message' do
|
||||||
it 'should publish the right message' do
|
it 'should publish the right message' do
|
||||||
user = pm.allowed_users.first
|
user = pm.allowed_users.first
|
||||||
|
user.groups << assign_allowed_group
|
||||||
assigner = described_class.new(pm, user)
|
assigner = described_class.new(pm, user)
|
||||||
|
|
||||||
assert_publish_topic_state(pm, user) { assigner.assign(user) }
|
assert_publish_topic_state(pm, user) { assigner.assign(user) }
|
||||||
|
@ -26,15 +30,16 @@ RSpec.describe TopicAssigner do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "assigning and unassigning" do
|
context "assigning and unassigning" do
|
||||||
|
before { SiteSetting.assign_enabled = true }
|
||||||
|
|
||||||
let(:post) { Fabricate(:post) }
|
let(:post) { Fabricate(:post) }
|
||||||
let(:topic) { post.topic }
|
let(:topic) { post.topic }
|
||||||
let(:moderator) { Fabricate(:moderator) }
|
let(:moderator) { Fabricate(:moderator, groups: [assign_allowed_group]) }
|
||||||
let(:moderator2) { Fabricate(:moderator) }
|
let(:moderator2) { Fabricate(:moderator, groups: [assign_allowed_group]) }
|
||||||
let(:assigner) { TopicAssigner.new(topic, moderator2) }
|
let(:assigner) { TopicAssigner.new(topic, moderator2) }
|
||||||
let(:assigner_self) { TopicAssigner.new(topic, moderator) }
|
let(:assigner_self) { TopicAssigner.new(topic, moderator) }
|
||||||
|
|
||||||
it "can assign and unassign correctly" do
|
it "can assign and unassign correctly" do
|
||||||
|
|
||||||
messages = MessageBus.track_publish("/notification-alert/#{moderator.id}") do
|
messages = MessageBus.track_publish("/notification-alert/#{moderator.id}") do
|
||||||
assigner.assign(moderator)
|
assigner.assign(moderator)
|
||||||
end
|
end
|
||||||
|
@ -87,7 +92,7 @@ RSpec.describe TopicAssigner do
|
||||||
|
|
||||||
context "when assigns_by_staff_mention is set to true" do
|
context "when assigns_by_staff_mention is set to true" do
|
||||||
let(:system_user) { Discourse.system_user }
|
let(:system_user) { Discourse.system_user }
|
||||||
let(:moderator) { Fabricate(:admin, username: "modi") }
|
let(:moderator) { Fabricate(:admin, username: "modi", groups: [assign_allowed_group]) }
|
||||||
let(:post) { Fabricate(:post, raw: "Hey you @system, stay unassigned", user: moderator) }
|
let(:post) { Fabricate(:post, raw: "Hey you @system, stay unassigned", user: moderator) }
|
||||||
let(:topic) { post.topic }
|
let(:topic) { post.topic }
|
||||||
|
|
||||||
|
@ -170,7 +175,7 @@ RSpec.describe TopicAssigner do
|
||||||
context "unassign_on_close" do
|
context "unassign_on_close" do
|
||||||
let(:post) { Fabricate(:post) }
|
let(:post) { Fabricate(:post) }
|
||||||
let(:topic) { post.topic }
|
let(:topic) { post.topic }
|
||||||
let(:moderator) { Fabricate(:moderator) }
|
let(:moderator) { Fabricate(:moderator, groups: [assign_allowed_group]) }
|
||||||
let(:assigner) { TopicAssigner.new(topic, moderator) }
|
let(:assigner) { TopicAssigner.new(topic, moderator) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Group do
|
||||||
|
describe 'Tracking changes that could affect the allow assign on groups site setting' do
|
||||||
|
let(:group) { Fabricate(:group) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
SiteSetting.assign_enabled = true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates the site setting when the group name changes' do
|
||||||
|
SiteSetting.assign_allowed_on_groups = "#{group.name}|staff|moderators"
|
||||||
|
different_name = 'different_name'
|
||||||
|
|
||||||
|
group.update!(name: different_name)
|
||||||
|
|
||||||
|
expect(SiteSetting.assign_allowed_on_groups).to eq "#{different_name}|staff|moderators"
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:removed_group_setting) { 'staff|moderators' }
|
||||||
|
|
||||||
|
it 'removes the group from the setting when the group gets destroyed' do
|
||||||
|
SiteSetting.assign_allowed_on_groups = "#{group.name}|staff|moderators"
|
||||||
|
|
||||||
|
group.destroy!
|
||||||
|
|
||||||
|
expect(SiteSetting.assign_allowed_on_groups).to eq removed_group_setting
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes the group from the setting when this is the last one on the list' do
|
||||||
|
SiteSetting.assign_allowed_on_groups = "staff|moderators|#{group.name}"
|
||||||
|
|
||||||
|
group.destroy!
|
||||||
|
|
||||||
|
expect(SiteSetting.assign_allowed_on_groups).to eq removed_group_setting
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes the group from the list when it is on the middle of the list' do
|
||||||
|
SiteSetting.assign_allowed_on_groups = "staff|#{group.name}|moderators"
|
||||||
|
|
||||||
|
group.destroy!
|
||||||
|
|
||||||
|
expect(SiteSetting.assign_allowed_on_groups).to eq removed_group_setting
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,17 +4,64 @@ require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe DiscourseAssign::AssignController do
|
RSpec.describe DiscourseAssign::AssignController do
|
||||||
|
|
||||||
let(:user) { Fabricate(:admin) }
|
before { SiteSetting.assign_enabled = true }
|
||||||
|
|
||||||
|
let(:default_allowed_group) { Group.find_by(name: 'staff') }
|
||||||
|
let(:user) { Fabricate(:admin, groups: [default_allowed_group]) }
|
||||||
let(:post) { Fabricate(:post) }
|
let(:post) { Fabricate(:post) }
|
||||||
let(:user2) { Fabricate(:active_user) }
|
let(:user2) { Fabricate(:active_user) }
|
||||||
|
|
||||||
|
describe 'only allow users from allowed groups' do
|
||||||
|
before { sign_in(user2) }
|
||||||
|
|
||||||
|
it 'filters requests where current_user is not member of an allowed group' do
|
||||||
|
SiteSetting.assign_allowed_on_groups = ''
|
||||||
|
|
||||||
|
put '/assign/assign.json', params: {
|
||||||
|
topic_id: post.topic_id, username: user2.username
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
end
|
||||||
|
|
||||||
|
context '#suggestions' do
|
||||||
before { sign_in(user) }
|
before { sign_in(user) }
|
||||||
|
|
||||||
|
it 'includes users in allowed groups' do
|
||||||
|
allowed_group = Group.find_by(name: 'everyone')
|
||||||
|
user2.groups << allowed_group
|
||||||
|
user2.groups << default_allowed_group
|
||||||
|
SiteSetting.assign_allowed_on_groups = 'staff|everyone'
|
||||||
|
TopicAssigner.new(post.topic, user).assign(user2)
|
||||||
|
|
||||||
|
get '/assign/suggestions.json'
|
||||||
|
suggestions = JSON.parse(response.body).map { |u| u['username'] }
|
||||||
|
|
||||||
|
expect(suggestions).to contain_exactly(user2.username, user.username)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not include users from disallowed groups' do
|
||||||
|
allowed_group = Group.find_by(name: 'everyone')
|
||||||
|
user2.groups << allowed_group
|
||||||
|
SiteSetting.assign_allowed_on_groups = 'staff'
|
||||||
|
TopicAssigner.new(post.topic, user).assign(user2)
|
||||||
|
|
||||||
|
get '/assign/suggestions.json'
|
||||||
|
suggestions = JSON.parse(response.body).map { |u| u['username'] }
|
||||||
|
|
||||||
|
expect(suggestions).to contain_exactly(user.username)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "#suggestions" do
|
context "#suggestions" do
|
||||||
before { SiteSetting.max_assigned_topics = 1 }
|
before do
|
||||||
|
SiteSetting.max_assigned_topics = 1
|
||||||
|
sign_in(user)
|
||||||
|
end
|
||||||
|
|
||||||
it 'excludes other users from the suggestions when they already reached the max assigns limit' do
|
it 'excludes other users from the suggestions when they already reached the max assigns limit' do
|
||||||
another_admin = Fabricate(:admin)
|
another_admin = Fabricate(:admin, groups: [default_allowed_group])
|
||||||
TopicAssigner.new(post.topic, user).assign(another_admin)
|
TopicAssigner.new(post.topic, user).assign(another_admin)
|
||||||
|
|
||||||
get '/assign/suggestions.json'
|
get '/assign/suggestions.json'
|
||||||
|
@ -25,6 +72,10 @@ RSpec.describe DiscourseAssign::AssignController do
|
||||||
end
|
end
|
||||||
|
|
||||||
context '#assign' do
|
context '#assign' do
|
||||||
|
before do
|
||||||
|
sign_in(user)
|
||||||
|
end
|
||||||
|
|
||||||
it 'assigns topic to a user' do
|
it 'assigns topic to a user' do
|
||||||
put '/assign/assign.json', params: {
|
put '/assign/assign.json', params: {
|
||||||
topic_id: post.topic_id, username: user2.username
|
topic_id: post.topic_id, username: user2.username
|
||||||
|
|
|
@ -84,7 +84,8 @@ RSpec.describe TopicListSerializer do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'as a staff' do
|
describe 'as a staff' do
|
||||||
let(:guardian) { Guardian.new(Fabricate(:admin)) }
|
let(:admin) { Fabricate(:admin, groups: [Group.find_by(name: 'staff')]) }
|
||||||
|
let(:guardian) { Guardian.new(admin) }
|
||||||
|
|
||||||
it 'should include the right attribute' do
|
it 'should include the right attribute' do
|
||||||
expect(serializer.as_json[:topic_list][:assigned_messages_count])
|
expect(serializer.as_json[:topic_list][:assigned_messages_count])
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { acceptance } from "helpers/qunit-helpers";
|
import { acceptance, replaceCurrentUser } from "helpers/qunit-helpers";
|
||||||
import { clearCallbacks } from "select-kit/mixins/plugin-api";
|
import { clearCallbacks } from "select-kit/mixins/plugin-api";
|
||||||
|
|
||||||
acceptance("Assign disabled mobile", {
|
acceptance("Assign disabled mobile", {
|
||||||
|
@ -11,6 +11,7 @@ acceptance("Assign disabled mobile", {
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test("Footer dropdown does not contain button", async assert => {
|
QUnit.test("Footer dropdown does not contain button", async assert => {
|
||||||
|
replaceCurrentUser({ can_assign: true });
|
||||||
const menu = selectKit(".topic-footer-mobile-dropdown");
|
const menu = selectKit(".topic-footer-mobile-dropdown");
|
||||||
|
|
||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { acceptance } from "helpers/qunit-helpers";
|
import { acceptance, replaceCurrentUser } from "helpers/qunit-helpers";
|
||||||
import { clearCallbacks } from "select-kit/mixins/plugin-api";
|
import { clearCallbacks } from "select-kit/mixins/plugin-api";
|
||||||
|
|
||||||
acceptance("Assign mobile", {
|
acceptance("Assign mobile", {
|
||||||
|
@ -11,6 +11,7 @@ acceptance("Assign mobile", {
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test("Footer dropdown contains button", async assert => {
|
QUnit.test("Footer dropdown contains button", async assert => {
|
||||||
|
replaceCurrentUser({ can_assign: true });
|
||||||
const menu = selectKit(".topic-footer-mobile-dropdown");
|
const menu = selectKit(".topic-footer-mobile-dropdown");
|
||||||
|
|
||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
|
|
Loading…
Reference in New Issue