From ae3d3db3f4d2d2e11a82d549d4af01b2013c22f8 Mon Sep 17 00:00:00 2001 From: pablonyx Date: Sat, 22 Mar 2025 11:21:31 -0700 Subject: [PATCH] Update slack bot listing endpoint (#4325) * update slack bot listing endpoint * nit --- backend/onyx/server/manage/slack_bot.py | 10 +-- .../channels/SlackChannelConfigFormFields.tsx | 8 +-- web/src/components/Dropdown.tsx | 62 +++++++++++++++++-- 3 files changed, 67 insertions(+), 13 deletions(-) diff --git a/backend/onyx/server/manage/slack_bot.py b/backend/onyx/server/manage/slack_bot.py index 0da66a5f607..1b0ebfa29e5 100644 --- a/backend/onyx/server/manage/slack_bot.py +++ b/backend/onyx/server/manage/slack_bot.py @@ -371,7 +371,9 @@ def get_all_channels_from_slack_api( _: User | None = Depends(current_admin_user), ) -> list[SlackChannel]: """ - Fetches channels the bot is a member of from the Slack API. + Fetches all channels in the Slack workspace using the conversations_list API. + This includes both public and private channels that are visible to the app, + not just the ones the bot is a member of. Handles pagination with a limit to avoid excessive API calls. """ tokens = fetch_slack_bot_tokens(db_session, bot_id) @@ -386,20 +388,20 @@ def get_all_channels_from_slack_api( current_page = 0 try: - # Use users_conversations with limited pagination + # Use conversations_list to get all channels in the workspace (including ones the bot is not a member of) while current_page < MAX_SLACK_PAGES: current_page += 1 # Make API call with cursor if we have one if next_cursor: - response = client.users_conversations( + response = client.conversations_list( types="public_channel,private_channel", exclude_archived=True, cursor=next_cursor, limit=SLACK_API_CHANNELS_PER_PAGE, ) else: - response = client.users_conversations( + response = client.conversations_list( types="public_channel,private_channel", exclude_archived=True, limit=SLACK_API_CHANNELS_PER_PAGE, diff --git a/web/src/app/admin/bots/[bot-id]/channels/SlackChannelConfigFormFields.tsx b/web/src/app/admin/bots/[bot-id]/channels/SlackChannelConfigFormFields.tsx index 9222205b8dd..67192b39d12 100644 --- a/web/src/app/admin/bots/[bot-id]/channels/SlackChannelConfigFormFields.tsx +++ b/web/src/app/admin/bots/[bot-id]/channels/SlackChannelConfigFormFields.tsx @@ -254,14 +254,14 @@ export function SlackChannelConfigFormFields({ onSearchTermChange={(term) => { form.setFieldValue("channel_name", term); }} + allowCustomValues={true} /> )}

- Note: This list shows public and private channels where the - bot is a member (up to 500 channels). If you don't see a - channel, make sure the bot is added to that channel in Slack - first, or type the channel name manually. + Note: This list shows existing public and private channels (up + to 500). You can either select from the list or type any + channel name directly.

)} diff --git a/web/src/components/Dropdown.tsx b/web/src/components/Dropdown.tsx index 83cdf26d9dd..c74ddc72279 100644 --- a/web/src/components/Dropdown.tsx +++ b/web/src/components/Dropdown.tsx @@ -54,6 +54,7 @@ export function SearchMultiSelectDropdown({ onDelete, onSearchTermChange, initialSearchTerm = "", + allowCustomValues = false, }: { options: StringOrNumberOption[]; onSelect: (selected: StringOrNumberOption) => void; @@ -62,6 +63,7 @@ export function SearchMultiSelectDropdown({ onDelete?: (name: string) => void; onSearchTermChange?: (term: string) => void; initialSearchTerm?: string; + allowCustomValues?: boolean; }) { const [isOpen, setIsOpen] = useState(false); const [searchTerm, setSearchTerm] = useState(initialSearchTerm); @@ -77,12 +79,29 @@ export function SearchMultiSelectDropdown({ option.name.toLowerCase().includes(searchTerm.toLowerCase()) ); + // Handle selecting a custom value not in the options list + const handleCustomValueSelect = () => { + if (allowCustomValues && searchTerm.trim() !== "") { + const customOption: StringOrNumberOption = { + name: searchTerm, + value: searchTerm, + }; + onSelect(customOption); + setIsOpen(false); + } + }; + useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( dropdownRef.current && !dropdownRef.current.contains(event.target as Node) ) { + // If allowCustomValues is enabled and there's text in the search field, + // treat clicking outside as selecting the custom value + if (allowCustomValues && searchTerm.trim() !== "") { + handleCustomValueSelect(); + } setIsOpen(false); } }; @@ -91,7 +110,7 @@ export function SearchMultiSelectDropdown({ return () => { document.removeEventListener("mousedown", handleClickOutside); }; - }, []); + }, [allowCustomValues, searchTerm]); useEffect(() => { setSearchTerm(initialSearchTerm); @@ -102,17 +121,33 @@ export function SearchMultiSelectDropdown({
) => { - setSearchTerm(e.target.value); - if (e.target.value) { + const newValue = e.target.value; + setSearchTerm(newValue); + if (onSearchTermChange) { + onSearchTermChange(newValue); + } + if (newValue) { setIsOpen(true); } else { setIsOpen(false); } }} onFocus={() => setIsOpen(true)} + onKeyDown={(e) => { + if ( + e.key === "Enter" && + allowCustomValues && + searchTerm.trim() !== "" + ) { + e.preventDefault(); + handleCustomValueSelect(); + } + }} className="inline-flex justify-between w-full px-4 py-2 text-sm bg-white dark:bg-transparent text-text-800 border border-background-300 rounded-md shadow-sm" /> + )} + {onCreate && searchTerm.trim() !== "" && !filteredOptions.some( @@ -177,7 +228,8 @@ export function SearchMultiSelectDropdown({ )} {filteredOptions.length === 0 && - (!onCreate || searchTerm.trim() === "") && ( + ((!onCreate && !allowCustomValues) || + searchTerm.trim() === "") && (
No matches found