From 0046041e9e20b8d9e17bb7539280bc65b873a321 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Fri, 20 Aug 2021 12:22:37 +0200 Subject: [PATCH] FEATURE: improves random assign automation (#166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Makes a best attempt at being random and assigning people who haven’t been assigned for a long time - Uses timezones and holidays - Allows to define a minimum delay between assignments - Creates a post if no one is available --- config/locales/client.en.yml | 2 + config/locales/server.en.yml | 1 + lib/random_assign_utils.rb | 37 ++++++++++++++++++ plugin.rb | 75 +++++++++++++++++++++++++++++++++--- 4 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 lib/random_assign_utils.rb diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 260bab0..0db7639 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -75,5 +75,7 @@ en: fields: assignees_group: label: Assignees Group + minimum_time_between_assignments: + label: Hours between assignments assigned_topic: label: Assigned Topic ID diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 603d0c1..c19ebb6 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -66,3 +66,4 @@ en: scriptables: random_assign: title: Random assign + no_one: "Attempted randomly assign a member of @%{group}, but no one was available." diff --git a/lib/random_assign_utils.rb b/lib/random_assign_utils.rb new file mode 100644 index 0000000..0360e9d --- /dev/null +++ b/lib/random_assign_utils.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class RandomAssignUtils + def self.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 + + def self.no_one!(topic_id, group) + PostCreator.create!( + Discourse.system_user, + topic_id: topic_id, + raw: I18n.t("discourse_automation.scriptables.random_assign.no_one", group: group), + validate: false + ) + end + + def self.in_working_hours?(user_id) + tzinfo = RandomAssignUtils.user_tzinfo(user_id) + tztime = tzinfo.now + + !tztime.saturday? && + !tztime.sunday? && + tztime.hour > 7 && + tztime.hour < 11 + end +end diff --git a/plugin.rb b/plugin.rb index bb6fa4e..8a6d27c 100644 --- a/plugin.rb +++ b/plugin.rb @@ -539,9 +539,12 @@ after_initialize do end if defined?(DiscourseAutomation) + require 'random_assign_utils' + add_automation_scriptable('random_assign') do field :assignees_group, component: :group field :assigned_topic, component: :text + field :minimum_time_between_assignments, component: :text version 1 @@ -550,14 +553,76 @@ after_initialize do script do |context, fields| next unless SiteSetting.assign_enabled? - next unless group_id = fields.dig('assignees_group', 'value') - next unless group = Group.find_by(id: group_id) - assign_to = group.group_users.order(Arel.sql('RANDOM()')).first.user - next unless topic_id = fields.dig('assigned_topic', 'value') next unless topic = Topic.find_by(id: topic_id) - TopicAssigner.new(topic, Discourse.system_user).assign(assign_to) + next unless group_id = fields.dig('assignees_group', 'value') + next unless group = Group.find_by(id: group_id) + + min_hours = (fields.dig('minimum_time_between_assignments', 'value') || 12).to_i + if TopicCustomField + .where(name: 'assigned_to_id', topic_id: topic_id) + .where('created_at < ?', min_hours.hours.ago) + .exists? + next + end + + users_on_holiday = Set.new( + User + .where(id: + UserCustomField + .where(name: 'on_holiday', value: 't') + .pluck(:user_id) + ).pluck(:id) + ) + + group_users_ids = group + .group_users + .joins(:user) + .pluck('users.id') + .reject { |user_id| users_on_holiday.include?(user_id) } + + if group_users_ids.empty? + RandomAssignUtils.no_one!(topic_id, group.name) + next + end + + last_assignees_ids = UserAction + .joins(:user) + .where(action_type: UserAction::ASSIGNED, target_topic_id: topic_id) + .where('user_actions.created_at > ?', 6.months.ago) + .order(created_at: :desc) + .limit(group_users_ids.length) + .pluck('users.id') + .uniq + + users_ids = group_users_ids - last_assignees_ids + if users_ids.blank? + recently_assigned_users_ids = UserAction + .joins(:user) + .where(action_type: UserAction::ASSIGNED, target_topic_id: topic_id) + .where('user_actions.created_at < ?', 2.weeks.ago) + .pluck('users.id') + .uniq + users_ids = group_users_ids - recently_assigned_users_ids + end + + if users_ids.blank? + RandomAssignUtils.no_one!(topic_id, group.name) + next + end + + assign_to_user_id = users_ids.shuffle.find do |user_id| + RandomAssignUtils.in_working_hours?(user_id) + end + + if assign_to_user_id.blank? + RandomAssignUtils.no_one!(topic_id, group.name) + next + end + + assign_to = User.find_by(id: assign_to_user_id) + assign_to && TopicAssigner.new(topic, Discourse.system_user).assign(assign_to) end end end