FIX: Find better users for automatic assignment (#370)

There were two problems with the way current automation script for automatic
assignment works:

- it tried to assign users that were not allowed to be assigned because they
were not part of groups that are allowed to use discourse-assign - fixed by
skipping users that are not a part of assign_allowed_on_groups groups

- it assigned new users - fixed by adding a new user that can skip new users
This commit is contained in:
Bianca Nenciu 2022-08-25 15:13:33 +03:00 committed by GitHub
parent ba2eedb874
commit 4046c9fb40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 146 additions and 63 deletions

View File

@ -175,46 +175,40 @@ module DiscourseAssign
end
def user_menu_assigns
assign_notifications = Notification.unread_type(
current_user,
Notification.types[:assigned],
user_menu_limit
)
assign_notifications =
Notification.unread_type(current_user, Notification.types[:assigned], user_menu_limit)
if assign_notifications.size < user_menu_limit
opts = {}
ignored_assignment_ids = assign_notifications.filter_map do |notification|
notification.data_hash[:assignment_id]
end
ignored_assignment_ids =
assign_notifications.filter_map { |notification| notification.data_hash[:assignment_id] }
opts[:ignored_assignment_ids] = ignored_assignment_ids if ignored_assignment_ids.present?
assigns_list = TopicQuery.new(
current_user,
per_page: user_menu_limit - assign_notifications.size
).list_messages_assigned(current_user, ignored_assignment_ids: ignored_assignment_ids)
assigns_list =
TopicQuery.new(
current_user,
per_page: user_menu_limit - assign_notifications.size,
).list_messages_assigned(current_user, ignored_assignment_ids: ignored_assignment_ids)
end
if assign_notifications.present?
serialized_notifications = ActiveModel::ArraySerializer.new(
assign_notifications,
each_serializer: NotificationSerializer,
scope: guardian
)
serialized_notifications =
ActiveModel::ArraySerializer.new(
assign_notifications,
each_serializer: NotificationSerializer,
scope: guardian,
)
end
if assigns_list
serialized_assigns = serialize_data(
assigns_list,
TopicListSerializer,
scope: guardian,
root: false
)[:topics]
serialized_assigns =
serialize_data(assigns_list, TopicListSerializer, scope: guardian, root: false)[:topics]
end
render json: {
notifications: serialized_notifications || [],
topics: serialized_assigns || [],
}
notifications: serialized_notifications || [],
topics: serialized_assigns || [],
}
end
private

View File

@ -126,6 +126,9 @@ en:
min_recently_assigned_days:
label: Min recently assigned days
description: Defaults to 14 days.
skip_new_users_for_days:
label: Skip new users for days
description: Defaults to 0 days (users can be assigned immediately after signing up).
max_recently_assigned_days:
label: Max recently assigned days
description: First attempt to exclude users assigned in the last `n` days. If no user left, fallbacks to `min_recently_assigned_days`. Defaults to 180 days.

View File

@ -38,6 +38,7 @@ class RandomAssignUtils
raise_error(automation, "Group(#{group_id}) not found")
end
assignable_user_ids = User.assign_allowed.pluck(:id)
users_on_holiday =
Set.new(
User.where(
@ -45,11 +46,15 @@ class RandomAssignUtils
).pluck(:id),
)
group_users = group.group_users.joins(:user)
if skip_new_users_for_days = fields.dig("skip_new_users_for_days", "value").presence
group_users = group_users.where("users.created_at < ?", skip_new_users_for_days.to_i.days.ago)
end
group_users_ids =
group
.group_users
.joins(:user)
group_users
.pluck("users.id")
.filter { |user_id| assignable_user_ids.include?(user_id) }
.reject { |user_id| users_on_holiday.include?(user_id) }
if group_users_ids.empty?

View File

@ -400,9 +400,7 @@ after_initialize do
SQL
where_args = { user_id: user.id }
if ignored_assignment_ids.present?
where_args[:ignored_assignment_ids] = ignored_assignment_ids
end
where_args[:ignored_assignment_ids] = ignored_assignment_ids if ignored_assignment_ids.present?
list = list.where("topics.id IN (#{topic_ids_sql})", **where_args).includes(:allowed_users)
create_list(:assigned, { unordered: true }, list)
@ -978,6 +976,7 @@ after_initialize do
field :minimum_time_between_assignments, component: :text
field :max_recently_assigned_days, component: :text
field :min_recently_assigned_days, component: :text
field :skip_new_users_for_days, component: :text
field :in_working_hours, component: :boolean
field :post_template, component: :post

View File

@ -44,7 +44,7 @@ describe RandomAssignUtils do
automation,
)
expect(topic_1.posts.first.raw).to match(
"Attempted randomly assign a member of `@#{group_1.name}`, but no one was available.",
I18n.t("discourse_automation.scriptables.random_assign.no_one", group: group_1.name),
)
end
end
@ -73,7 +73,33 @@ describe RandomAssignUtils do
automation,
)
expect(topic_1.posts.first.raw).to match(
"Attempted randomly assign a member of `@#{group_1.name}`, but no one was available.",
I18n.t("discourse_automation.scriptables.random_assign.no_one", group: group_1.name),
)
end
end
context "no users can be assigned because none are members of assign_allowed_on_groups groups" do
fab!(:topic_1) { Fabricate(:topic) }
fab!(:group_1) { Fabricate(:group) }
fab!(:user_1) { Fabricate(:user) }
before { group_1.add(user_1) }
it "creates post on the topic" do
described_class.automation_script!(
{},
{
"assignees_group" => {
"value" => group_1.id,
},
"assigned_topic" => {
"value" => topic_1.id,
},
},
automation,
)
expect(topic_1.posts.first.raw).to match(
I18n.t("discourse_automation.scriptables.random_assign.no_one", group: group_1.name),
)
end
end
@ -158,7 +184,7 @@ describe RandomAssignUtils do
automation,
)
expect(topic_1.posts.first.raw).to match(
"Attempted randomly assign a member of `@#{group_1.name}`, but no one was available.",
I18n.t("discourse_automation.scriptables.random_assign.no_one", group: group_1.name),
)
end
end
@ -250,6 +276,59 @@ describe RandomAssignUtils do
end
end
end
context "skip_new_users_for_days is set" do
fab!(:topic_1) { Fabricate(:topic) }
fab!(:post_1) { Fabricate(:post, topic: topic_1) }
fab!(:group_1) { Fabricate(:group) }
fab!(:user_1) { Fabricate(:user) }
before do
SiteSetting.assign_allowed_on_groups = "#{group_1.id}"
group_1.add(user_1)
end
it "creates post on the topic if all users are new" do
described_class.automation_script!(
{},
{
"assignees_group" => {
"value" => group_1.id,
},
"assigned_topic" => {
"value" => topic_1.id,
},
"skip_new_users_for_days" => {
"value" => "10",
},
},
automation,
)
expect(topic_1.posts.last.raw).to match(
I18n.t("discourse_automation.scriptables.random_assign.no_one", group: group_1.name),
)
end
it "assign topic if all users are old" do
described_class.automation_script!(
{},
{
"assignees_group" => {
"value" => group_1.id,
},
"assigned_topic" => {
"value" => topic_1.id,
},
"skip_new_users_for_days" => {
"value" => "0",
},
},
automation,
)
expect(topic_1.assignment).not_to eq(nil)
end
end
end
describe ".recently_assigned_users_ids" do

View File

@ -401,7 +401,9 @@ RSpec.describe DiscourseAssign::AssignController do
fab!(:read_assigned_post) { Fabricate(:post, topic: Fabricate(:post).topic) }
fab!(:read_assigned_post_in_same_topic) { Fabricate(:post, topic: Fabricate(:post).topic) }
fab!(:unread_assigned_post_in_same_topic) { Fabricate(:post, topic: read_assigned_post_in_same_topic.topic) }
fab!(:unread_assigned_post_in_same_topic) do
Fabricate(:post, topic: read_assigned_post_in_same_topic.topic)
end
fab!(:another_user_unread_assigned_topic) { Fabricate(:post).topic }
fab!(:another_user_read_assigned_topic) { Fabricate(:post).topic }
@ -416,9 +418,7 @@ RSpec.describe DiscourseAssign::AssignController do
read_assigned_post,
unread_assigned_post_in_same_topic,
read_assigned_post_in_same_topic,
].each do |target|
Assigner.new(target, normal_admin).assign(user)
end
].each { |target| Assigner.new(target, normal_admin).assign(user) }
Notification
.where(
@ -428,25 +428,23 @@ RSpec.describe DiscourseAssign::AssignController do
topic_id: [
read_assigned_topic.id,
read_assigned_post.topic.id,
read_assigned_post_in_same_topic.topic.id
]
read_assigned_post_in_same_topic.topic.id,
],
)
.where.not(
topic_id: read_assigned_post_in_same_topic.topic.id,
post_number: unread_assigned_post_in_same_topic.post_number
post_number: unread_assigned_post_in_same_topic.post_number,
)
.update_all(read: true)
Assigner.new(another_user_read_assigned_topic, normal_admin).assign(user2)
Assigner.new(another_user_unread_assigned_topic, normal_admin).assign(user2)
Notification
.where(
notification_type: Notification.types[:assigned],
read: false,
user_id: user2.id,
topic_id: another_user_read_assigned_topic,
)
.update_all(read: true)
Notification.where(
notification_type: Notification.types[:assigned],
read: false,
user_id: user2.id,
topic_id: another_user_read_assigned_topic,
).update_all(read: true)
end
context "when logged out" do
@ -457,9 +455,7 @@ RSpec.describe DiscourseAssign::AssignController do
end
context "when logged in" do
before do
sign_in(user)
end
before { sign_in(user) }
it "responds with 403 if the current user can't assign" do
user.update!(admin: false)
@ -479,11 +475,16 @@ RSpec.describe DiscourseAssign::AssignController do
expect(response.status).to eq(200)
notifications = response.parsed_body["notifications"]
expect(notifications.map { |n| [n["topic_id"], n["post_number"]] }).to eq([
[unread_assigned_topic.id, 1],
[unread_assigned_post.topic.id, unread_assigned_post.post_number],
[unread_assigned_post_in_same_topic.topic.id, unread_assigned_post_in_same_topic.post_number]
])
expect(notifications.map { |n| [n["topic_id"], n["post_number"]] }).to eq(
[
[unread_assigned_topic.id, 1],
[unread_assigned_post.topic.id, unread_assigned_post.post_number],
[
unread_assigned_post_in_same_topic.topic.id,
unread_assigned_post_in_same_topic.post_number,
],
],
)
end
it "responds with an array of assigned topics that are not associated with any of the unread assigned notifications" do
@ -491,11 +492,13 @@ RSpec.describe DiscourseAssign::AssignController do
expect(response.status).to eq(200)
topics = response.parsed_body["topics"]
expect(topics.map { |t| t["id"] }).to eq([
read_assigned_post_in_same_topic.topic.id,
read_assigned_post.topic.id,
read_assigned_topic.id,
])
expect(topics.map { |t| t["id"] }).to eq(
[
read_assigned_post_in_same_topic.topic.id,
read_assigned_post.topic.id,
read_assigned_topic.id,
],
)
end
it "fills up the remaining of the UsersController::USER_MENU_LIST_LIMIT limit with assigned topics" do