Fixed slack groups (#1814)

* Simplified slackbot response groups and fixed need more help bug

* mypy fixes

* added exceptions for the couldnt find passthrough arrays
This commit is contained in:
hagen-danswer
2024-07-13 15:34:35 -07:00
committed by GitHub
parent c7af6a4601
commit 36da2e4b27
11 changed files with 137 additions and 96 deletions

View File

@ -0,0 +1,45 @@
"""combined slack id fields
Revision ID: d716b0791ddd
Revises: 7aea705850d5
Create Date: 2024-07-10 17:57:45.630550
"""
from alembic import op
# revision identifiers, used by Alembic.
revision = "d716b0791ddd"
down_revision = "7aea705850d5"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.execute(
"""
UPDATE slack_bot_config
SET channel_config = jsonb_set(
channel_config,
'{respond_member_group_list}',
coalesce(channel_config->'respond_team_member_list', '[]'::jsonb) ||
coalesce(channel_config->'respond_slack_group_list', '[]'::jsonb)
) - 'respond_team_member_list' - 'respond_slack_group_list'
"""
)
def downgrade() -> None:
op.execute(
"""
UPDATE slack_bot_config
SET channel_config = jsonb_set(
jsonb_set(
channel_config - 'respond_member_group_list',
'{respond_team_member_list}',
'[]'::jsonb
),
'{respond_slack_group_list}',
'[]'::jsonb
)
"""
)

View File

@ -474,7 +474,7 @@ def build_follow_up_resolved_blocks(
if tag_str:
tag_str += " "
group_str = " ".join([f"<!subteam^{group}>" for group in group_ids])
group_str = " ".join([f"<!subteam^{group_id}|>" for group_id in group_ids])
if group_str:
group_str += " "

View File

@ -29,8 +29,8 @@ from danswer.danswerbot.slack.handlers.handle_regular_answer import (
from danswer.danswerbot.slack.models import SlackMessageInfo
from danswer.danswerbot.slack.utils import build_feedback_id
from danswer.danswerbot.slack.utils import decompose_action_id
from danswer.danswerbot.slack.utils import fetch_groupids_from_names
from danswer.danswerbot.slack.utils import fetch_userids_from_emails
from danswer.danswerbot.slack.utils import fetch_group_ids_from_names
from danswer.danswerbot.slack.utils import fetch_user_ids_from_emails
from danswer.danswerbot.slack.utils import get_channel_name_from_id
from danswer.danswerbot.slack.utils import get_feedback_visibility
from danswer.danswerbot.slack.utils import read_slack_thread
@ -43,7 +43,7 @@ from danswer.document_index.document_index_utils import get_both_index_names
from danswer.document_index.factory import get_default_document_index
from danswer.utils.logger import setup_logger
logger_base = setup_logger()
logger = setup_logger()
def handle_doc_feedback_button(
@ -51,7 +51,7 @@ def handle_doc_feedback_button(
client: SocketModeClient,
) -> None:
if not (actions := req.payload.get("actions")):
logger_base.error("Missing actions. Unable to build the source feedback view")
logger.error("Missing actions. Unable to build the source feedback view")
return
# Extracts the feedback_id coming from the 'source feedback' button
@ -134,7 +134,7 @@ def handle_generate_answer_button(
receiver_ids=None,
client=client.web_client,
channel=channel_id,
logger=cast(logging.Logger, logger_base),
logger=cast(logging.Logger, logger),
feedback_reminder_id=None,
)
@ -196,7 +196,7 @@ def handle_slack_feedback(
feedback=feedback,
)
else:
logger_base.error(f"Feedback type '{feedback_type}' not supported")
logger.error(f"Feedback type '{feedback_type}' not supported")
if get_feedback_visibility() == FeedbackVisibility.PRIVATE or feedback_type not in [
LIKE_BLOCK_ACTION_ID,
@ -260,11 +260,11 @@ def handle_followup_button(
tag_names = slack_bot_config.channel_config.get("follow_up_tags")
remaining = None
if tag_names:
tag_ids, remaining = fetch_userids_from_emails(
tag_ids, remaining = fetch_user_ids_from_emails(
tag_names, client.web_client
)
if remaining:
group_ids, _ = fetch_groupids_from_names(remaining, client.web_client)
group_ids, _ = fetch_group_ids_from_names(remaining, client.web_client)
blocks = build_follow_up_resolved_blocks(tag_ids=tag_ids, group_ids=group_ids)
@ -339,7 +339,7 @@ def handle_followup_resolved_button(
)
if not response.get("ok"):
logger_base.error("Unable to delete message for resolved")
logger.error("Unable to delete message for resolved")
if immediate:
msg_text = f"{clicker_name} has marked this question as resolved!"

View File

@ -18,8 +18,8 @@ from danswer.danswerbot.slack.handlers.handle_standard_answers import (
)
from danswer.danswerbot.slack.models import SlackMessageInfo
from danswer.danswerbot.slack.utils import ChannelIdAdapter
from danswer.danswerbot.slack.utils import fetch_userids_from_emails
from danswer.danswerbot.slack.utils import fetch_userids_from_groups
from danswer.danswerbot.slack.utils import fetch_user_ids_from_emails
from danswer.danswerbot.slack.utils import fetch_user_ids_from_groups
from danswer.danswerbot.slack.utils import respond_in_thread
from danswer.danswerbot.slack.utils import slack_usage_report
from danswer.danswerbot.slack.utils import update_emote_react
@ -158,11 +158,8 @@ def handle_message(
]
prompt = persona.prompts[0] if persona.prompts else None
# List of user id to send message to, if None, send to everyone in channel
send_to: list[str] | None = None
respond_tag_only = False
respond_team_member_list = None
respond_slack_group_list = None
respond_member_group_list = None
channel_conf = None
if slack_bot_config and slack_bot_config.channel_config:
@ -184,8 +181,7 @@ def handle_message(
)
respond_tag_only = channel_conf.get("respond_tag_only") or False
respond_team_member_list = channel_conf.get("respond_team_member_list") or None
respond_slack_group_list = channel_conf.get("respond_slack_group_list") or None
respond_member_group_list = channel_conf.get("respond_member_group_list", None)
if respond_tag_only and not bypass_filters:
logger.info(
@ -194,17 +190,23 @@ def handle_message(
)
return False
if respond_team_member_list:
send_to, _ = fetch_userids_from_emails(respond_team_member_list, client)
if respond_slack_group_list:
user_ids, _ = fetch_userids_from_groups(respond_slack_group_list, client)
send_to = (send_to + user_ids) if send_to else user_ids
if send_to:
send_to = list(set(send_to)) # remove duplicates
# List of user id to send message to, if None, send to everyone in channel
send_to: list[str] | None = None
missing_users: list[str] | None = None
if respond_member_group_list:
send_to, missing_ids = fetch_user_ids_from_emails(
respond_member_group_list, client
)
user_ids, missing_users = fetch_user_ids_from_groups(missing_ids, client)
send_to = list(set(send_to + user_ids)) if send_to else user_ids
if missing_users:
logger.warning(f"Failed to find these users/groups: {missing_users}")
# If configured to respond to team members only, then cannot be used with a /DanswerBot command
# which would just respond to the sender
if (respond_team_member_list or respond_slack_group_list) and is_bot_msg:
if send_to and is_bot_msg:
if sender_id:
respond_in_thread(
client=client,

View File

@ -302,7 +302,7 @@ def get_channel_name_from_id(
raise e
def fetch_userids_from_emails(
def fetch_user_ids_from_emails(
user_emails: list[str], client: WebClient
) -> tuple[list[str], list[str]]:
user_ids: list[str] = []
@ -318,57 +318,72 @@ def fetch_userids_from_emails(
return user_ids, failed_to_find
def fetch_userids_from_groups(
group_names: list[str], client: WebClient
def fetch_user_ids_from_groups(
given_names: list[str], client: WebClient
) -> tuple[list[str], list[str]]:
user_ids: list[str] = []
failed_to_find: list[str] = []
for group_name in group_names:
try:
# First, find the group ID from the group name
response = client.usergroups_list()
groups = {group["name"]: group["id"] for group in response["usergroups"]}
group_id = groups.get(group_name)
try:
response = client.usergroups_list()
if not isinstance(response.data, dict):
logger.error("Error fetching user groups")
return user_ids, given_names
if group_id:
# Fetch user IDs for the group
all_group_data = response.data.get("usergroups", [])
name_id_map = {d["name"]: d["id"] for d in all_group_data}
handle_id_map = {d["handle"]: d["id"] for d in all_group_data}
for given_name in given_names:
group_id = name_id_map.get(given_name) or handle_id_map.get(
given_name.lstrip("@")
)
if not group_id:
failed_to_find.append(given_name)
continue
try:
response = client.usergroups_users_list(usergroup=group_id)
user_ids.extend(response["users"])
else:
failed_to_find.append(group_name)
except Exception as e:
logger.error(f"Error fetching user IDs for group {group_name}: {str(e)}")
failed_to_find.append(group_name)
if isinstance(response.data, dict):
user_ids.extend(response.data.get("users", []))
else:
failed_to_find.append(given_name)
except Exception as e:
logger.error(f"Error fetching user group ids: {str(e)}")
failed_to_find.append(given_name)
except Exception as e:
logger.error(f"Error fetching user groups: {str(e)}")
failed_to_find = given_names
return user_ids, failed_to_find
def fetch_groupids_from_names(
names: list[str], client: WebClient
def fetch_group_ids_from_names(
given_names: list[str], client: WebClient
) -> tuple[list[str], list[str]]:
group_ids: set[str] = set()
group_data: list[str] = []
failed_to_find: list[str] = []
try:
response = client.usergroups_list()
if response.get("ok") and "usergroups" in response.data:
all_groups_dicts = response.data["usergroups"] # type: ignore
name_id_map = {d["name"]: d["id"] for d in all_groups_dicts}
handle_id_map = {d["handle"]: d["id"] for d in all_groups_dicts}
for group in names:
if group in name_id_map:
group_ids.add(name_id_map[group])
elif group in handle_id_map:
group_ids.add(handle_id_map[group])
else:
failed_to_find.append(group)
else:
# Most likely a Slack App scope issue
if not isinstance(response.data, dict):
logger.error("Error fetching user groups")
return group_data, given_names
all_group_data = response.data.get("usergroups", [])
name_id_map = {d["name"]: d["id"] for d in all_group_data}
handle_id_map = {d["handle"]: d["id"] for d in all_group_data}
for given_name in given_names:
id = handle_id_map.get(given_name.lstrip("@"))
id = id or name_id_map.get(given_name)
if id:
group_data.append(id)
else:
failed_to_find.append(given_name)
except Exception as e:
failed_to_find = given_names
logger.error(f"Error fetching user groups: {str(e)}")
return list(group_ids), failed_to_find
return group_data, failed_to_find
def fetch_user_semantic_id_from_id(

View File

@ -1121,8 +1121,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]]
respond_slack_group_list: NotRequired[list[str]]
respond_member_group_list: NotRequired[list[str]]
answer_filters: NotRequired[list[AllowedAnswerFilters]]
# If None then no follow up
# If empty list, follow up with no tags

View File

@ -157,8 +157,7 @@ class SlackBotConfigCreationRequest(BaseModel):
respond_to_bots: bool = False
enable_auto_filters: bool = False
# If no team members, assume respond in the channel to everyone
respond_team_member_list: list[str] = []
respond_slack_group_list: list[str] = []
respond_member_group_list: list[str] = []
answer_filters: list[AllowedAnswerFilters] = []
# list of user emails
follow_up_tags: list[str] | None = None

View File

@ -34,11 +34,8 @@ def _form_channel_config(
) -> ChannelConfig:
raw_channel_names = slack_bot_config_creation_request.channel_names
respond_tag_only = slack_bot_config_creation_request.respond_tag_only
respond_team_member_list = (
slack_bot_config_creation_request.respond_team_member_list
)
respond_slack_group_list = (
slack_bot_config_creation_request.respond_slack_group_list
respond_member_group_list = (
slack_bot_config_creation_request.respond_member_group_list
)
answer_filters = slack_bot_config_creation_request.answer_filters
follow_up_tags = slack_bot_config_creation_request.follow_up_tags
@ -61,7 +58,7 @@ def _form_channel_config(
detail=str(e),
)
if respond_tag_only and (respond_team_member_list or respond_slack_group_list):
if respond_tag_only and respond_member_group_list:
raise ValueError(
"Cannot set DanswerBot to only respond to tags only and "
"also respond to a predetermined set of users."
@ -72,10 +69,8 @@ def _form_channel_config(
}
if respond_tag_only is not None:
channel_config["respond_tag_only"] = respond_tag_only
if respond_team_member_list:
channel_config["respond_team_member_list"] = respond_team_member_list
if respond_slack_group_list:
channel_config["respond_slack_group_list"] = respond_slack_group_list
if respond_member_group_list:
channel_config["respond_member_group_list"] = respond_member_group_list
if answer_filters:
channel_config["answer_filters"] = answer_filters
if follow_up_tags is not None: