Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

FEATURE: Add script to modify user group memberships through badges #206

Merged
merged 1 commit into from Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions config/locales/client.en.yml
Expand Up @@ -225,6 +225,16 @@ en:
info: Info
success: Success
error: Error
user_group_membership_through_badge:
fields:
badge_name:
label: Badge Name
group:
label: Group
description: Target group. Users with the specified badge will be added to this group
remove_members_without_badge:
label: Remove existing members without badge
description: Optional, Remove existing group members without the specified badge
suspend_user_by_email:
fields:
suspend_until:
Expand Down
3 changes: 3 additions & 0 deletions config/locales/server.en.yml
Expand Up @@ -91,6 +91,9 @@ en:
user_global_notice:
title: User global notice
description: Allows to display a global notice for a user
user_group_membership_through_badge:
title: User Group Membership through Badge
description: Modify user group membership based on a badge
suspend_user_by_email_with_api_call:
doc: When triggering `suspend_user_by_email` with an api call, the endpoint expects a valid `email` to be present in the params sent. `reasons` and `suspend_until (ISO 8601 format)` can also be used to override default fields values.
user_global_notice_with_stalled_topic:
Expand Down
@@ -0,0 +1,96 @@
# frozen_string_literal: true

DiscourseAutomation::Scriptable::USER_GROUP_MEMBERSHIP_THROUGH_BADGE =
"user_group_membership_through_badge"
DiscourseAutomation::Scriptable::USER_GROUP_MEMBERSHIP_THROUGH_BADGE_BULK_MODIFY_START_COUNT = 1000
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷🏾 Arbitrary limit alert. I wonder if this is even needed, the add_user_to_group_through_custom_field script doesn't do this at the moment.

Better safe than sorry, we probably don't want to be doing 1000s of Group#add calls anyways


DiscourseAutomation::Scriptable.add(
DiscourseAutomation::Scriptable::USER_GROUP_MEMBERSHIP_THROUGH_BADGE,
) do
version 1

field :badge_name, component: :text, required: true
field :group, component: :group, required: true
field :remove_members_without_badge, component: :boolean

triggerables %i[recurring user_first_logged_in]

script do |context, fields|
badge_name = fields.dig("badge_name", "value").strip
group_id = fields.dig("group", "value")
remove_members_without_badge = fields.dig("remove_members_without_badge", "value")
current_user = context["user"]
bulk_modify_start_count =
DiscourseAutomation::Scriptable::USER_GROUP_MEMBERSHIP_THROUGH_BADGE_BULK_MODIFY_START_COUNT

badge = Badge.find_by(name: badge_name)
unless badge
Rails.logger.warn("[discourse-automation] Couldn’t find badge with name #{badge_name}")
next
end

group = Group.find_by(id: group_id)
unless group
Rails.logger.warn("[discourse-automation] Couldn’t find group with id #{group_id}")
next
end

query_options = { group_id: group.id, badge_id: badge.id }

# IDs of users who currently have badge but not members of target group
user_ids_to_add_query = +<<~SQL
SELECT u.id AS user_id
FROM users u
JOIN user_badges ub ON u.id = ub.user_id
LEFT JOIN group_users gu ON u.id = gu.user_id AND gu.group_id = :group_id
WHERE ub.badge_id = :badge_id AND gu.user_id IS NULL
SQL

if current_user
user_ids_to_add_query << " AND u.id = :user_id"
query_options[:user_id] = current_user.id
end

user_ids_to_add = DB.query_single(user_ids_to_add_query, query_options)

if user_ids_to_add.count < bulk_modify_start_count
User
.where(id: user_ids_to_add)
.each do |user|
group.add(user)
GroupActionLogger.new(Discourse.system_user, group).log_add_user_to_group(user)
end
else
group.bulk_add(user_ids_to_add)
end
Comment on lines +56 to +65
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we always do bulk_add ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How essential are the user_added_to_group and user_removed_from_group events? The bulk methods understandably don't trigger these.

I considered going all in with the bulk methods but decided against it for two main reasons:

  1. We lose the events and ability to trigger other automations off of the badge-group sync
  2. Compatibility with exisiting add_user_to_group_through_custom_field script. It does not perform bulk adds


next unless remove_members_without_badge

# IDs of users who are currently target group members without the badge
user_ids_to_remove_query = +<<~SQL
SELECT u.id AS user_id
FROM users u
JOIN group_users gu ON u.id = gu.user_id
LEFT JOIN user_badges ub ON u.id = ub.user_id AND ub.badge_id = :badge_id
WHERE gu.group_id = :group_id AND ub.user_id IS NULL
SQL

if current_user
user_ids_to_remove_query << " AND u.id = :user_id"
query_options[:user_id] ||= current_user.id
end

user_ids_to_remove = DB.query_single(user_ids_to_remove_query, query_options)

if user_ids_to_remove.count < bulk_modify_start_count
User
.where(id: user_ids_to_remove)
.each do |user|
group.remove(user)
GroupActionLogger.new(Discourse.system_user, group).log_remove_user_from_group(user)
end
else
group.bulk_remove(user_ids_to_remove)
end
Comment on lines +85 to +94
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same, maybe only bulk_remove ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

end
end
1 change: 1 addition & 0 deletions plugin.rb
Expand Up @@ -53,6 +53,7 @@ module ::DiscourseAutomation
lib/discourse_automation/scripts/suspend_user_by_email
lib/discourse_automation/scripts/topic_required_words
lib/discourse_automation/scripts/user_global_notice
lib/discourse_automation/scripts/user_group_membership_through_badge
lib/discourse_automation/scripts/zapier_webhook
lib/discourse_automation/triggers/after_post_cook
lib/discourse_automation/triggers/api_call
Expand Down