Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: escalate breaches of quota to escalation group #82

Merged
merged 9 commits into from
Apr 5, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions app/controllers/discourse_chatbot/chatbot_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ def start_bot_convo
bot_user = ::User.find_by(username: bot_username)
channel_type = SiteSetting.chatbot_quick_access_talk_button

evaluation = ::DiscourseChatbot::EventEvaluation.new
over_quota = evaluation.over_quota(current_user.id)

if channel_type == "chat"

bot_author = ::User.find_by(username: SiteSetting.chatbot_bot_user)
Expand All @@ -37,11 +40,11 @@ def start_bot_convo

last_chat = ::Chat::Message.find_by(id: chat_channel.latest_not_deleted_message_id)

if (last_chat && last_chat.message != I18n.t("chatbot.quick_access_kick_off.announcement")) || last_chat.nil?
if (last_chat && (over_quota && last_chat.message != I18n.t('chatbot.errors.overquota') || !over_quota && last_chat.message != I18n.t("chatbot.quick_access_kick_off.announcement"))) || last_chat.nil?
Chat::CreateMessage.call(
chat_channel_id: chat_channel_id,
guardian: guardian,
message: I18n.t("chatbot.quick_access_kick_off.announcement"),
message: over_quota ? I18n.t('chatbot.errors.overquota') : I18n.t("chatbot.quick_access_kick_off.announcement"),
)
end

Expand All @@ -50,7 +53,7 @@ def start_bot_convo
elsif channel_type == "personal message"
default_opts = {
post_alert_options: { skip_send_email: true },
raw: I18n.t("chatbot.quick_access_kick_off.announcement"),
raw: over_quota ? I18n.t('chatbot.errors.overquota') : I18n.t("chatbot.quick_access_kick_off.announcement"),
skip_validations: true,
title: I18n.t("chatbot.pm_prefix"),
archetype: Archetype.private_message,
Expand Down
4 changes: 4 additions & 0 deletions app/jobs/scheduled/chatbot_quota_reset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ def execute(args)
current_queries = "0"
UserCustomField.create!(user_id: u.id, name: ::DiscourseChatbot::CHATBOT_QUERIES_CUSTOM_FIELD, value: current_queries)
end

if current_record = UserCustomField.find_by(user_id: u.id, name: ::DiscourseChatbot::CHATBOT_QUERIES_QUOTA_REACH_ESCALATION_DATE_CUSTOM_FIELD)
current_record.delete
end
end

end
Expand Down
7 changes: 7 additions & 0 deletions config/locales/server.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ en:
chatbot_quota_high_trust: "The allowed number of bot responses allowed by prompting user per week - high trust groups"
chatbot_quota_medium_trust: "The allowed number of bot responses allowed by prompting user per week - medium trust groups"
chatbot_quota_low_trust: "The allowed number of bot responses allowed by prompting user per week - low trust groups"
chatbot_quota_reach_escalation_groups: "The group(s) to which the bot will escalate when the quota is reached"
chatbot_quota_reach_escalation_cool_down_period: "The number of days the bot will wait before escalating again after reaching the quota"
chatbot_high_trust_groups: "Groups considered high trust for bot interaction"
chatbot_medium_trust_groups: "Groups considered medium trust for bot interaction"
chatbot_low_trust_groups: "Groups considered low trust for bot interaction"
Expand Down Expand Up @@ -73,6 +75,10 @@ en:
pm_prefix: "A Discussion with Chatbot"
quick_access_kick_off:
announcement: "Hello, how can I help you?"
quota_reached:
escalation:
title: "Chatbot Quota Reached: %{username}"
message: "I'm afraid user '%{username}' has reached their weekly quota for Chatbot interactions. I'm escalating this issue to staff for further assistance."
prompt:
system:
basic:
Expand Down Expand Up @@ -134,6 +140,7 @@ en:
title: "Support bot chat escalation"
announcement: "This is a follow-up Personal Message which includes Staff. The following chat conversation was had between the user and the bot:\n\n%{content}"
answer_summary: "The function ran and this chat has been successfully escalated and raised to the attention of staff. A Personal Message to discuss things further with Staff has been created at the following url: %{url}. I must now share this news & the url with the user. There is no need for me to do anything more than communicate this fact with the web link and wish the user a good day."
no_escalation_groups: "No escalation groups have been set up for this function. Please contact your administrator."
wrong_type_error: "This function cannot be used outside of Chat Messages"
error: "Escalation of user and bot chat failed"
forum_get_user_address:
Expand Down
12 changes: 12 additions & 0 deletions config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,18 @@ plugins:
chatbot_quota_low_trust:
default: 10
client: false
chatbot_quota_reach_escalation_groups:
client: false
type: group_list
list_type: compact
default: ""
allow_any: false
refresh: true
chatbot_quota_reach_escalation_cool_down_period:
client: false
default: 1
min: 0
max: 7
chatbot_max_look_behind:
default: 5
max: 160
Expand Down
46 changes: 45 additions & 1 deletion lib/discourse_chatbot/event_evaluation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,51 @@ def over_quota(user_id)
UserCustomField.create!(user_id: user_id, name: CHATBOT_QUERIES_CUSTOM_FIELD, value: current_queries)
end

current_queries > max_quota
breach = current_queries > max_quota
escalate_as_required(user_id) if breach
breach
end

def escalate_as_required(user_id)
escalation_date = UserCustomField.find_by(name: ::DiscourseChatbot::CHATBOT_QUERIES_QUOTA_REACH_ESCALATION_DATE_CUSTOM_FIELD, user_id: user_id)

if SiteSetting.chatbot_quota_reach_escalation_cool_down_period > 0
if escalation_date.nil? || !SiteSetting.chatbot_quota_reach_escalation_cool_down_period.nil? &&
escalation_date.value < SiteSetting.chatbot_quota_reach_escalation_cool_down_period.days.ago
escalate_quota_breach(user_id)
end
end
end

def escalate_quota_breach(user_id)
user = User.find_by(id: user_id)
system_user = User.find_by(username_lower: "system")

target_group_names = []

Array(SiteSetting.chatbot_quota_reach_escalation_groups).each do |g|
unless g.to_i == 0
target_group_names << Group.find(g.to_i).name
end
end

if !target_group_names.empty?
target_group_names = target_group_names.join(",")

default_opts = {
post_alert_options: { skip_send_email: true },
raw: I18n.t("chatbot.quota_reached.escalation.message", username: user.username),
skip_validations: true,
title: I18n.t("chatbot.quota_reached.escalation.title", username: user.username),
archetype: Archetype.private_message,
target_group_names: target_group_names
}

post = PostCreator.create!(system_user, default_opts)

user.custom_fields[::DiscourseChatbot::CHATBOT_QUERIES_QUOTA_REACH_ESCALATION_DATE_CUSTOM_FIELD] = DateTime.now
user.save_custom_fields
end
end

private
Expand Down
42 changes: 24 additions & 18 deletions lib/discourse_chatbot/functions/escalate_to_staff_function.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,32 +37,38 @@ def process(args, opts)
target_group_names = []

Array(SiteSetting.chatbot_escalate_to_staff_groups).each do |g|
target_group_names << Group.find(g.to_i).name
unless g.to_i == 0
target_group_names << Group.find(g.to_i).name
end
end

target_group_names = target_group_names.join(",")
if !target_group_names.empty?
target_group_names = target_group_names.join(",")

message_or_post_id = opts[:reply_to_message_or_post_id]
message_or_post_id = opts[:reply_to_message_or_post_id]

message_collection = get_messages(message_or_post_id)

content = generate_transcript(message_collection, bot_user)
message_collection = get_messages(message_or_post_id)

default_opts = {
post_alert_options: { skip_send_email: true },
raw: I18n.t("chatbot.prompt.function.escalate_to_staff.announcement", content: content),
skip_validations: true,
title: I18n.t("chatbot.prompt.function.escalate_to_staff.title"),
archetype: Archetype.private_message,
target_usernames: target_usernames,
target_group_names: target_group_names
}
content = generate_transcript(message_collection, bot_user)

post = PostCreator.create!(current_user, default_opts)
default_opts = {
post_alert_options: { skip_send_email: true },
raw: I18n.t("chatbot.prompt.function.escalate_to_staff.announcement", content: content),
skip_validations: true,
title: I18n.t("chatbot.prompt.function.escalate_to_staff.title"),
archetype: Archetype.private_message,
target_usernames: target_usernames,
target_group_names: target_group_names
}

url = "https://#{Discourse.current_hostname}/t/slug/#{post.topic_id}"
post = PostCreator.create!(current_user, default_opts)

response = I18n.t("chatbot.prompt.function.escalate_to_staff.answer_summary", url: url)
url = "https://#{Discourse.current_hostname}/t/slug/#{post.topic_id}"

response = I18n.t("chatbot.prompt.function.escalate_to_staff.answer_summary", url: url)
else
response = I18n.t("chatbot.prompt.function.escalate_to_staff.no_escalation_groups")
end
rescue
I18n.t("chatbot.prompt.function.escalate_to_staff.error", parameter: args[parameters[0][:name]])
end
Expand Down
2 changes: 1 addition & 1 deletion lib/discourse_chatbot/post/post_evaluation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def trigger_response(submission)

::DiscourseChatbot.progress_debug_message("humans found in this convo: #{human_participants_count}")

if bot_user && (user != bot_user) && (mentions_bot_name || explicit_reply_to_bot || (last_post_was_bot && human_participants_count == 1))
if bot_user && (user.id > 0) && (mentions_bot_name || explicit_reply_to_bot || (last_post_was_bot && human_participants_count == 1))
opts = {
type: POST,
private: topic.archetype == Archetype.private_message,
Expand Down
5 changes: 4 additions & 1 deletion plugin.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# name: discourse-chatbot
# about: a plugin that allows you to have a conversation with a configurable chatbot in Discourse Chat, Topics and Private Messages
# version: 0.9.7
# version: 0.9.8
# authors: merefield
# url: https://github.com/merefield/discourse-chatbot

Expand All @@ -22,6 +22,7 @@ module ::DiscourseChatbot
POST = "post"
MESSAGE = "message"
CHATBOT_QUERIES_CUSTOM_FIELD = "chatbot_queries"
CHATBOT_QUERIES_QUOTA_REACH_ESCALATION_DATE_CUSTOM_FIELD = "chatbot_queries_quota_reach_escalation_date"
POST_TYPES_REGULAR_ONLY = [1]
POST_TYPES_INC_WHISPERS = [1, 4]

Expand Down Expand Up @@ -111,6 +112,8 @@ def progress_debug_message(message)

register_user_custom_field_type(::DiscourseChatbot::CHATBOT_QUERIES_CUSTOM_FIELD, :integer)

register_user_custom_field_type(::DiscourseChatbot::CHATBOT_QUERIES_QUOTA_REACH_ESCALATION_DATE_CUSTOM_FIELD, :date)


add_to_serializer(:current_user, :chatbot_access) do
!::DiscourseChatbot::EventEvaluation.new.trust_level(object.id).blank?
Expand Down
20 changes: 19 additions & 1 deletion spec/lib/event_evaluation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require_relative '../plugin_helper'

CHATBOT_QUERIES_CUSTOM_FIELD = "chatbot_queries"
CHATBOT_QUERIES_QUOTA_REACH_ESCALATION_DATE_CUSTOM_FIELD = "chatbot_queries_quota_reach_escalation_date"

describe ::DiscourseChatbot::EventEvaluation do
let(:normal_user) { Fabricate(:user, trust_level: TrustLevel[1], refresh_auto_groups: true) }
Expand All @@ -12,6 +13,7 @@

before(:each) do
SiteSetting.chatbot_enabled = true
SiteSetting.chatbot_quota_reach_escalation_groups = "3"
end

it "returns the correct trust level for user in high trust group" do
Expand Down Expand Up @@ -54,7 +56,7 @@
expect(event.over_quota(high_trust_user.id)).to equal(false)
end

it "returns the correct quota decision if user is in high trust group and user is outside of quota" do
it "returns the correct quota decision if user is in high trust group and user is outside of quota and escalates" do
UserCustomField.create!(user_id: high_trust_user.id, name: CHATBOT_QUERIES_CUSTOM_FIELD, value: 3)
SiteSetting.chatbot_high_trust_groups = "13|14"
SiteSetting.chatbot_medium_trust_groups = "11|12"
Expand All @@ -64,6 +66,22 @@
SiteSetting.chatbot_quota_low_trust = 1

event = ::DiscourseChatbot::EventEvaluation.new
expect { event.over_quota(high_trust_user.id) }.to change { Topic.where(archetype: Archetype.private_message).count }.by(1)
expect(event.over_quota(high_trust_user.id)).to equal(true)
end

it "returns the correct quota decision if user is in high trust group and user is outside of quota but doesn't escalate" do
UserCustomField.create!(user_id: high_trust_user.id, name: CHATBOT_QUERIES_CUSTOM_FIELD, value: 3)
UserCustomField.create!(user_id: high_trust_user.id, name: CHATBOT_QUERIES_QUOTA_REACH_ESCALATION_DATE_CUSTOM_FIELD, value: 30.minutes.ago)
SiteSetting.chatbot_high_trust_groups = "13|14"
SiteSetting.chatbot_medium_trust_groups = "11|12"
SiteSetting.chatbot_low_trust_groups = "10"
SiteSetting.chatbot_quota_high_trust = 3
SiteSetting.chatbot_quota_medium_trust = 2
SiteSetting.chatbot_quota_low_trust = 1

event = ::DiscourseChatbot::EventEvaluation.new
expect { event.over_quota(high_trust_user.id) }.not_to change { Topic.where(archetype: Archetype.private_message).count }
expect(event.over_quota(high_trust_user.id)).to equal(true)
end

Expand Down
Loading