mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-06-02 11:09:20 +02:00
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:
parent
c7af6a4601
commit
36da2e4b27
@ -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
|
||||
)
|
||||
"""
|
||||
)
|
@ -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 += " "
|
||||
|
||||
|
@ -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!"
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -79,13 +79,9 @@ export const SlackBotCreationForm = ({
|
||||
existingSlackBotConfig?.channel_config?.respond_to_bots || false,
|
||||
enable_auto_filters:
|
||||
existingSlackBotConfig?.enable_auto_filters || false,
|
||||
respond_member_group_list: (
|
||||
respond_member_group_list:
|
||||
existingSlackBotConfig?.channel_config
|
||||
?.respond_team_member_list ?? []
|
||||
).concat(
|
||||
existingSlackBotConfig?.channel_config
|
||||
?.respond_slack_group_list ?? []
|
||||
),
|
||||
?.respond_member_group_list ?? [],
|
||||
still_need_help_enabled:
|
||||
existingSlackBotConfig?.channel_config?.follow_up_tags !==
|
||||
undefined,
|
||||
@ -133,14 +129,7 @@ export const SlackBotCreationForm = ({
|
||||
channel_names: values.channel_names.filter(
|
||||
(channelName) => channelName !== ""
|
||||
),
|
||||
respond_team_member_list: values.respond_member_group_list.filter(
|
||||
(teamMemberEmail) =>
|
||||
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(teamMemberEmail)
|
||||
),
|
||||
respond_slack_group_list: values.respond_member_group_list.filter(
|
||||
(slackGroupName) =>
|
||||
!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(slackGroupName)
|
||||
),
|
||||
respond_member_group_list: values.respond_member_group_list,
|
||||
usePersona: usingPersonas,
|
||||
standard_answer_categories: values.standard_answer_categories.map(
|
||||
(category) => category.id
|
||||
@ -257,13 +246,13 @@ export const SlackBotCreationForm = ({
|
||||
/>
|
||||
<TextArrayField
|
||||
name="respond_member_group_list"
|
||||
label="Team Member Emails Or Slack Group Names"
|
||||
label="Team Member Emails Or Slack Group Names/Handles"
|
||||
subtext={`If specified, DanswerBot responses will only be
|
||||
visible to the members or groups in this list. This is
|
||||
useful if you want DanswerBot to operate in an
|
||||
"assistant" mode, where it helps the team members find
|
||||
answers, but let's them build on top of DanswerBot's response / throw
|
||||
out the occasional incorrect answer. Group names are case sensitive.`}
|
||||
out the occasional incorrect answer. Group names and handles are case sensitive.`}
|
||||
values={values}
|
||||
/>
|
||||
<Divider />
|
||||
|
@ -14,8 +14,7 @@ interface SlackBotConfigCreationRequest {
|
||||
questionmark_prefilter_enabled: boolean;
|
||||
respond_tag_only: boolean;
|
||||
respond_to_bots: boolean;
|
||||
respond_team_member_list: string[];
|
||||
respond_slack_group_list: string[];
|
||||
respond_member_group_list: string[];
|
||||
follow_up_tags?: string[];
|
||||
usePersona: boolean;
|
||||
response_type: SlackBotResponseType;
|
||||
@ -43,8 +42,7 @@ const buildRequestBodyFromCreationRequest = (
|
||||
respond_tag_only: creationRequest.respond_tag_only,
|
||||
respond_to_bots: creationRequest.respond_to_bots,
|
||||
enable_auto_filters: creationRequest.enable_auto_filters,
|
||||
respond_team_member_list: creationRequest.respond_team_member_list,
|
||||
respond_slack_group_list: creationRequest.respond_slack_group_list,
|
||||
respond_member_group_list: creationRequest.respond_member_group_list,
|
||||
answer_filters: buildFiltersFromCreationRequest(creationRequest),
|
||||
follow_up_tags: creationRequest.follow_up_tags?.filter((tag) => tag !== ""),
|
||||
...(creationRequest.usePersona
|
||||
|
@ -539,8 +539,7 @@ export interface ChannelConfig {
|
||||
channel_names: string[];
|
||||
respond_tag_only?: boolean;
|
||||
respond_to_bots?: boolean;
|
||||
respond_team_member_list?: string[];
|
||||
respond_slack_group_list?: string[];
|
||||
respond_member_group_list?: string[];
|
||||
answer_filters?: AnswerFilterOption[];
|
||||
follow_up_tags?: string[];
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user