mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 13:08:40 +08:00
Previously, assignment permissions only came from the global `assign_allowed_on_groups` site setting. This change lets categories grant assignment permission to additional groups, keeping assignment controls and assignee suggestions scoped to the relevant category.
205 lines
5.7 KiB
Ruby
Vendored
205 lines
5.7 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
require_relative "discourse_assign/assignment_permissions"
|
|
|
|
class RandomAssignUtils
|
|
attr_reader :context, :fields, :automation, :topic, :group
|
|
|
|
def self.automation_script!(...)
|
|
new(...).automation_script!
|
|
end
|
|
|
|
def initialize(context, fields, automation)
|
|
@context = context
|
|
@fields = fields
|
|
@automation = automation
|
|
|
|
raise_error("discourse-assign is not enabled") unless SiteSetting.assign_enabled?
|
|
unless topic_id = fields.dig("assigned_topic", "value")
|
|
raise_error("`assigned_topic` not provided")
|
|
end
|
|
unless @topic = Topic.find_by(id: topic_id)
|
|
raise_error("Topic(#{topic_id}) not found")
|
|
end
|
|
|
|
unless group_id = fields.dig("assignees_group", "value")
|
|
raise_error("`assignees_group` not provided")
|
|
end
|
|
unless @group = Group.find_by(id: group_id)
|
|
raise_error("Group(#{group_id}) not found")
|
|
end
|
|
end
|
|
|
|
def automation_script!
|
|
return log_info("Topic(#{topic.id}) has already been assigned recently") if assigned_recently?
|
|
return no_one! unless assigned_user
|
|
assign_user!
|
|
end
|
|
|
|
def recently_assigned_users_ids(from)
|
|
usernames =
|
|
PostCustomField
|
|
.joins(:post)
|
|
.where(
|
|
name: "action_code_who",
|
|
posts: {
|
|
topic: topic,
|
|
action_code: %w[assigned reassigned assigned_to_post],
|
|
},
|
|
)
|
|
.where("posts.created_at > ?", from)
|
|
.order("posts.created_at DESC")
|
|
.pluck(:value)
|
|
.uniq
|
|
User
|
|
.where(username: usernames)
|
|
.joins(
|
|
"JOIN unnest('{#{usernames.join(",")}}'::text[]) WITH ORDINALITY t(username, ord) USING(username)",
|
|
)
|
|
.limit(100)
|
|
.order("ord")
|
|
.pluck(:id)
|
|
end
|
|
|
|
private
|
|
|
|
def assigned_user
|
|
@assigned_user ||=
|
|
begin
|
|
group_users_ids = group_users.pluck(:id)
|
|
return if group_users_ids.empty?
|
|
|
|
last_assignees_ids = recently_assigned_users_ids(max_recently_assigned_days)
|
|
users_ids = group_users_ids - last_assignees_ids
|
|
if users_ids.blank?
|
|
recently_assigned_users_ids = recently_assigned_users_ids(min_recently_assigned_days)
|
|
users_ids = group_users_ids - recently_assigned_users_ids
|
|
end
|
|
users_ids << last_assignees_ids.intersection(group_users_ids).last if users_ids.blank?
|
|
if fields.dig("in_working_hours", "value")
|
|
assign_to_user_id = users_ids.shuffle.detect { |user_id| in_working_hours?(user_id) }
|
|
end
|
|
assign_to_user_id ||= users_ids.sample
|
|
|
|
User.find(assign_to_user_id)
|
|
end
|
|
end
|
|
|
|
def assign_user!
|
|
return create_post_template if post_template
|
|
Assigner
|
|
.new(topic, Discourse.system_user)
|
|
.assign(assigned_user, allow_self_reassign: true)
|
|
.then do |result|
|
|
next if result[:success]
|
|
no_one!
|
|
end
|
|
end
|
|
|
|
def create_post_template
|
|
post =
|
|
PostCreator.new(
|
|
Discourse.system_user,
|
|
raw: post_template,
|
|
skip_validations: true,
|
|
topic_id: topic.id,
|
|
).create!
|
|
Assigner
|
|
.new(post, Discourse.system_user)
|
|
.assign(assigned_user, allow_self_reassign: true)
|
|
.then do |result|
|
|
next if result[:success]
|
|
PostDestroyer.new(Discourse.system_user, post, context: "random assign rollback").destroy
|
|
no_one!
|
|
end
|
|
end
|
|
|
|
def group_users
|
|
users =
|
|
group
|
|
.users
|
|
.where(id: DiscourseAssign::AssignmentPermissions.allowed_user_ids_for_target(topic))
|
|
.where.not(
|
|
id:
|
|
User
|
|
.joins(:_custom_fields)
|
|
.where(user_custom_fields: { name: "on_holiday", value: "t" })
|
|
.select(:id),
|
|
)
|
|
return users unless skip_new_users_for_days
|
|
users.where("users.created_at < ?", skip_new_users_for_days)
|
|
end
|
|
|
|
def raise_error(message)
|
|
raise("[discourse-automation id=#{automation.id}] #{message}.")
|
|
end
|
|
|
|
def log_info(message)
|
|
Rails.logger.info("[discourse-automation id=#{automation.id}] #{message}.")
|
|
end
|
|
|
|
def no_one!
|
|
PostCreator.create!(
|
|
Discourse.system_user,
|
|
topic_id: topic.id,
|
|
raw: I18n.t("discourse_automation.scriptables.random_assign.no_one", group: group.name),
|
|
validate: false,
|
|
)
|
|
end
|
|
|
|
def assigned_recently?
|
|
return unless min_hours
|
|
TopicCustomField
|
|
.where(name: "assigned_to_id", topic: topic)
|
|
.where("created_at < ?", min_hours)
|
|
.exists?
|
|
end
|
|
|
|
def skip_new_users_for_days
|
|
days = fields.dig("skip_new_users_for_days", "value").presence
|
|
return unless days
|
|
days.to_i.days.ago
|
|
end
|
|
|
|
def max_recently_assigned_days
|
|
@max_days ||= (fields.dig("max_recently_assigned_days", "value").presence || 180).to_i.days.ago
|
|
end
|
|
|
|
def min_recently_assigned_days
|
|
@min_days ||= (fields.dig("min_recently_assigned_days", "value").presence || 14).to_i.days.ago
|
|
end
|
|
|
|
def post_template
|
|
@post_template ||= fields.dig("post_template", "value").presence
|
|
end
|
|
|
|
def min_hours
|
|
hours = fields.dig("minimum_time_between_assignments", "value").presence
|
|
return unless hours
|
|
hours.to_i.hours.ago
|
|
end
|
|
|
|
def in_working_hours?(user_id)
|
|
tzinfo = user_tzinfo(user_id)
|
|
tztime = tzinfo.now
|
|
|
|
!tztime.saturday? && !tztime.sunday? && tztime.hour > 7 && tztime.hour < 11
|
|
end
|
|
|
|
def user_tzinfo(user_id)
|
|
timezone = UserOption.where(user_id: user_id).pluck(:timezone).first || "UTC"
|
|
|
|
tzinfo = nil
|
|
begin
|
|
tzinfo = ActiveSupport::TimeZone.find_tzinfo(timezone)
|
|
rescue TZInfo::InvalidTimezoneIdentifier
|
|
Rails.logger.warn(
|
|
"#{User.find_by(id: user_id)&.username} has the timezone #{timezone} set, we do not know how to parse it in Rails (assuming UTC)",
|
|
)
|
|
timezone = "UTC"
|
|
tzinfo = ActiveSupport::TimeZone.find_tzinfo(timezone)
|
|
end
|
|
|
|
tzinfo
|
|
end
|
|
end
|