diff --git a/backend/alembic/versions/d716b0791ddd_combined_slack_id_fields.py b/backend/alembic/versions/d716b0791ddd_combined_slack_id_fields.py
new file mode 100644
index 0000000000..3f13d7c556
--- /dev/null
+++ b/backend/alembic/versions/d716b0791ddd_combined_slack_id_fields.py
@@ -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
+ )
+ """
+ )
diff --git a/backend/danswer/danswerbot/slack/blocks.py b/backend/danswer/danswerbot/slack/blocks.py
index aaf2631885..4c7931a020 100644
--- a/backend/danswer/danswerbot/slack/blocks.py
+++ b/backend/danswer/danswerbot/slack/blocks.py
@@ -474,7 +474,7 @@ def build_follow_up_resolved_blocks(
if tag_str:
tag_str += " "
- group_str = " ".join([f"" for group in group_ids])
+ group_str = " ".join([f"" for group_id in group_ids])
if group_str:
group_str += " "
diff --git a/backend/danswer/danswerbot/slack/handlers/handle_buttons.py b/backend/danswer/danswerbot/slack/handlers/handle_buttons.py
index 30c7015b96..b7b7bea9f4 100644
--- a/backend/danswer/danswerbot/slack/handlers/handle_buttons.py
+++ b/backend/danswer/danswerbot/slack/handlers/handle_buttons.py
@@ -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!"
diff --git a/backend/danswer/danswerbot/slack/handlers/handle_message.py b/backend/danswer/danswerbot/slack/handlers/handle_message.py
index a05006dec1..cd97af9684 100644
--- a/backend/danswer/danswerbot/slack/handlers/handle_message.py
+++ b/backend/danswer/danswerbot/slack/handlers/handle_message.py
@@ -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,
diff --git a/backend/danswer/danswerbot/slack/utils.py b/backend/danswer/danswerbot/slack/utils.py
index 1e5ffcc52f..b89b9f65f2 100644
--- a/backend/danswer/danswerbot/slack/utils.py
+++ b/backend/danswer/danswerbot/slack/utils.py
@@ -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(
diff --git a/backend/danswer/db/models.py b/backend/danswer/db/models.py
index f7d16743c6..3d33596a19 100644
--- a/backend/danswer/db/models.py
+++ b/backend/danswer/db/models.py
@@ -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
diff --git a/backend/danswer/server/manage/models.py b/backend/danswer/server/manage/models.py
index f544df0f24..80a2728c6f 100644
--- a/backend/danswer/server/manage/models.py
+++ b/backend/danswer/server/manage/models.py
@@ -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
diff --git a/backend/danswer/server/manage/slack_bot.py b/backend/danswer/server/manage/slack_bot.py
index d5f08e2694..b587badef0 100644
--- a/backend/danswer/server/manage/slack_bot.py
+++ b/backend/danswer/server/manage/slack_bot.py
@@ -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:
diff --git a/web/src/app/admin/bot/SlackBotConfigCreationForm.tsx b/web/src/app/admin/bot/SlackBotConfigCreationForm.tsx
index 23968d585f..881076eb41 100644
--- a/web/src/app/admin/bot/SlackBotConfigCreationForm.tsx
+++ b/web/src/app/admin/bot/SlackBotConfigCreationForm.tsx
@@ -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 = ({
/>
diff --git a/web/src/app/admin/bot/lib.ts b/web/src/app/admin/bot/lib.ts
index b3d6ec678b..c2d2b29150 100644
--- a/web/src/app/admin/bot/lib.ts
+++ b/web/src/app/admin/bot/lib.ts
@@ -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
diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts
index a507e1fa5b..60cb664b07 100644
--- a/web/src/lib/types.ts
+++ b/web/src/lib/types.ts
@@ -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[];
}