From 0060a1dd58370d95d0a2a7df991a9b868fc2156f Mon Sep 17 00:00:00 2001 From: Yuhong Sun Date: Thu, 1 Feb 2024 22:18:15 -0800 Subject: [PATCH] Immediate Mark Resolved SlackBot Option and Respond to Bots Option (#1031) --- backend/danswer/danswerbot/slack/blocks.py | 8 ++- backend/danswer/danswerbot/slack/constants.py | 1 + .../slack/handlers/handle_buttons.py | 57 +++++++++++++------ backend/danswer/danswerbot/slack/listener.py | 21 ++++++- backend/danswer/db/models.py | 1 + backend/danswer/server/manage/models.py | 1 + backend/danswer/server/manage/slack_bot.py | 4 ++ .../admin/bot/SlackBotConfigCreationForm.tsx | 8 +++ web/src/app/admin/bot/lib.ts | 2 + web/src/lib/types.ts | 1 + 10 files changed, 82 insertions(+), 22 deletions(-) diff --git a/backend/danswer/danswerbot/slack/blocks.py b/backend/danswer/danswerbot/slack/blocks.py index 3a84c18aa..8851ecf45 100644 --- a/backend/danswer/danswerbot/slack/blocks.py +++ b/backend/danswer/danswerbot/slack/blocks.py @@ -20,6 +20,7 @@ from danswer.danswerbot.slack.constants import DISLIKE_BLOCK_ACTION_ID from danswer.danswerbot.slack.constants import FEEDBACK_DOC_BUTTON_BLOCK_ACTION_ID from danswer.danswerbot.slack.constants import FOLLOWUP_BUTTON_ACTION_ID from danswer.danswerbot.slack.constants import FOLLOWUP_BUTTON_RESOLVED_ACTION_ID +from danswer.danswerbot.slack.constants import IMMEDIATE_RESOLVED_BUTTON_ACTION_ID from danswer.danswerbot.slack.constants import LIKE_BLOCK_ACTION_ID from danswer.danswerbot.slack.utils import build_feedback_id from danswer.danswerbot.slack.utils import remove_slack_text_interactions @@ -279,11 +280,16 @@ def build_follow_up_block(message_id: int | None) -> ActionsBlock: return ActionsBlock( block_id=build_feedback_id(message_id) if message_id is not None else None, elements=[ + ButtonElement( + action_id=IMMEDIATE_RESOLVED_BUTTON_ACTION_ID, + style="primary", + text="I'm all set!", + ), ButtonElement( action_id=FOLLOWUP_BUTTON_ACTION_ID, style="danger", text="I need more help from a human!", - ) + ), ], ) diff --git a/backend/danswer/danswerbot/slack/constants.py b/backend/danswer/danswerbot/slack/constants.py index f390e7818..a4930b593 100644 --- a/backend/danswer/danswerbot/slack/constants.py +++ b/backend/danswer/danswerbot/slack/constants.py @@ -1,6 +1,7 @@ LIKE_BLOCK_ACTION_ID = "feedback-like" DISLIKE_BLOCK_ACTION_ID = "feedback-dislike" FEEDBACK_DOC_BUTTON_BLOCK_ACTION_ID = "feedback-doc-button" +IMMEDIATE_RESOLVED_BUTTON_ACTION_ID = "immediate-resolved-button" FOLLOWUP_BUTTON_ACTION_ID = "followup-button" FOLLOWUP_BUTTON_RESOLVED_ACTION_ID = "followup-resolved-button" SLACK_CHANNEL_ID = "channel_id" diff --git a/backend/danswer/danswerbot/slack/handlers/handle_buttons.py b/backend/danswer/danswerbot/slack/handlers/handle_buttons.py index 0a88c0b96..0ca030612 100644 --- a/backend/danswer/danswerbot/slack/handlers/handle_buttons.py +++ b/backend/danswer/danswerbot/slack/handlers/handle_buttons.py @@ -192,14 +192,11 @@ def handle_followup_button( ) -def handle_followup_resolved_button( +def get_clicker_name( req: SocketModeRequest, client: SocketModeClient, -) -> None: - channel_id = req.payload["container"]["channel_id"] - message_ts = req.payload["container"]["message_ts"] - thread_ts = req.payload["container"]["thread_ts"] - clicker_backup_name = req.payload.get("user", {}).get("name", "Someone") +) -> str: + clicker_name = req.payload.get("user", {}).get("name", "Someone") clicker_real_name = None try: clicker = client.web_client.users_info(user=req.payload["user"]["id"]) @@ -210,6 +207,23 @@ def handle_followup_resolved_button( # Likely a scope issue pass + if clicker_real_name: + clicker_name = clicker_real_name + + return clicker_name + + +def handle_followup_resolved_button( + req: SocketModeRequest, + client: SocketModeClient, + immediate: bool = False, +) -> None: + channel_id = req.payload["container"]["channel_id"] + message_ts = req.payload["container"]["message_ts"] + thread_ts = req.payload["container"]["thread_ts"] + + clicker_name = get_clicker_name(req, client) + update_emote_react( emoji=DANSWER_FOLLOWUP_EMOJI, channel=channel_id, @@ -218,20 +232,27 @@ def handle_followup_resolved_button( client=client.web_client, ) - slack_call = make_slack_api_rate_limited(client.web_client.chat_delete) - response = slack_call( - channel=channel_id, - ts=message_ts, - ) + # Delete the message with the option to mark resolved + if not immediate: + slack_call = make_slack_api_rate_limited(client.web_client.chat_delete) + response = slack_call( + channel=channel_id, + ts=message_ts, + ) - if not response.get("ok"): - logger_base.error("Unable to delete message for resolved") + if not response.get("ok"): + logger_base.error("Unable to delete message for resolved") - resolved_block = SectionBlock( - text=f"{clicker_real_name or clicker_backup_name} has marked this question as resolved! " - f'\n\n You can always click the "I need more help button" to let the team ' - f"know that your problem still needs attention." - ) + if immediate: + msg_text = f"{clicker_name} has marked this question as resolved!" + else: + msg_text = ( + f"{clicker_name} has marked this question as resolved! " + f'\n\n You can always click the "I need more help button" to let the team ' + f"know that your problem still needs attention." + ) + + resolved_block = SectionBlock(text=msg_text) respond_in_thread( client=client.web_client, diff --git a/backend/danswer/danswerbot/slack/listener.py b/backend/danswer/danswerbot/slack/listener.py index 4f8404bac..5512d0909 100644 --- a/backend/danswer/danswerbot/slack/listener.py +++ b/backend/danswer/danswerbot/slack/listener.py @@ -19,6 +19,7 @@ from danswer.danswerbot.slack.constants import DISLIKE_BLOCK_ACTION_ID from danswer.danswerbot.slack.constants import FEEDBACK_DOC_BUTTON_BLOCK_ACTION_ID from danswer.danswerbot.slack.constants import FOLLOWUP_BUTTON_ACTION_ID from danswer.danswerbot.slack.constants import FOLLOWUP_BUTTON_RESOLVED_ACTION_ID +from danswer.danswerbot.slack.constants import IMMEDIATE_RESOLVED_BUTTON_ACTION_ID from danswer.danswerbot.slack.constants import LIKE_BLOCK_ACTION_ID from danswer.danswerbot.slack.constants import SLACK_CHANNEL_ID from danswer.danswerbot.slack.constants import VIEW_DOC_FEEDBACK_ID @@ -87,8 +88,20 @@ def prefilter_requests(req: SocketModeRequest, client: SocketModeClient) -> bool return False if event.get("bot_profile"): - channel_specific_logger.info("Ignoring message from bot") - return False + channel_name, _ = get_channel_name_from_id( + client=client.web_client, channel_id=channel + ) + + engine = get_sqlalchemy_engine() + with Session(engine) as db_session: + slack_bot_config = get_slack_bot_config_for_channel( + channel_name=channel_name, db_session=db_session + ) + if not slack_bot_config or not slack_bot_config.channel_config.get( + "respond_to_bots" + ): + channel_specific_logger.info("Ignoring message from bot") + return False # Ignore things like channel_join, channel_leave, etc. # NOTE: "file_share" is just a message with a file attachment, so we @@ -300,8 +313,10 @@ def action_routing(req: SocketModeRequest, client: SocketModeClient) -> None: return handle_doc_feedback_button(req, client) elif action["action_id"] == FOLLOWUP_BUTTON_ACTION_ID: return handle_followup_button(req, client) + elif action["action_id"] == IMMEDIATE_RESOLVED_BUTTON_ACTION_ID: + return handle_followup_resolved_button(req, client, immediate=True) elif action["action_id"] == FOLLOWUP_BUTTON_RESOLVED_ACTION_ID: - return handle_followup_resolved_button(req, client) + return handle_followup_resolved_button(req, client, immediate=False) def view_routing(req: SocketModeRequest, client: SocketModeClient) -> None: diff --git a/backend/danswer/db/models.py b/backend/danswer/db/models.py index 50867c1c2..606040a00 100644 --- a/backend/danswer/db/models.py +++ b/backend/danswer/db/models.py @@ -786,6 +786,7 @@ class ChannelConfig(TypedDict): channel_names: list[str] respond_tag_only: NotRequired[bool] # defaults to False + respond_to_bots: NotRequired[bool] # defaults to False respond_team_member_list: NotRequired[list[str]] answer_filters: NotRequired[list[AllowedAnswerFilters]] # If None then no follow up diff --git a/backend/danswer/server/manage/models.py b/backend/danswer/server/manage/models.py index ee4470eab..a22060511 100644 --- a/backend/danswer/server/manage/models.py +++ b/backend/danswer/server/manage/models.py @@ -75,6 +75,7 @@ class SlackBotConfigCreationRequest(BaseModel): persona_id: int | None # NOTE: only one of `document_sets` / `persona_id` should be set channel_names: list[str] respond_tag_only: bool = False + respond_to_bots: bool = False # If no team members, assume respond in the channel to everyone respond_team_member_list: list[str] = [] answer_filters: list[AllowedAnswerFilters] = [] diff --git a/backend/danswer/server/manage/slack_bot.py b/backend/danswer/server/manage/slack_bot.py index 3da02125e..9720f1f5a 100644 --- a/backend/danswer/server/manage/slack_bot.py +++ b/backend/danswer/server/manage/slack_bot.py @@ -77,6 +77,10 @@ def _form_channel_config( if follow_up_tags is not None: channel_config["follow_up_tags"] = follow_up_tags + channel_config[ + "respond_to_bots" + ] = slack_bot_config_creation_request.respond_to_bots + return channel_config diff --git a/web/src/app/admin/bot/SlackBotConfigCreationForm.tsx b/web/src/app/admin/bot/SlackBotConfigCreationForm.tsx index 8006ccf43..06efc724a 100644 --- a/web/src/app/admin/bot/SlackBotConfigCreationForm.tsx +++ b/web/src/app/admin/bot/SlackBotConfigCreationForm.tsx @@ -69,6 +69,8 @@ export const SlackBotCreationForm = ({ ).includes("questionmark_prefilter"), respond_tag_only: existingSlackBotConfig?.channel_config?.respond_tag_only || false, + respond_to_bots: + existingSlackBotConfig?.channel_config?.respond_to_bots || false, respond_team_member_list: existingSlackBotConfig?.channel_config ?.respond_team_member_list || ([] as string[]), @@ -94,6 +96,7 @@ export const SlackBotCreationForm = ({ answer_validity_check_enabled: Yup.boolean().required(), questionmark_prefilter_enabled: Yup.boolean().required(), respond_tag_only: Yup.boolean().required(), + respond_to_bots: Yup.boolean().required(), respond_team_member_list: Yup.array().of(Yup.string()).required(), still_need_help_enabled: Yup.boolean().required(), follow_up_tags: Yup.array().of(Yup.string()), @@ -187,6 +190,11 @@ export const SlackBotCreationForm = ({ label="Respond to @DanswerBot Only" subtext="If set, DanswerBot will only respond when directly tagged" /> + tag !== ""), diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts index 437f20659..62955d948 100644 --- a/web/src/lib/types.ts +++ b/web/src/lib/types.ts @@ -351,6 +351,7 @@ export type AnswerFilterOption = export interface ChannelConfig { channel_names: string[]; respond_tag_only?: boolean; + respond_to_bots?: boolean; respond_team_member_list?: string[]; answer_filters?: AnswerFilterOption[]; follow_up_tags?: string[];