Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 0 additions & 83 deletions backend/onyx/server/manage/slack_bot.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
from sqlalchemy.orm import Session

from onyx.auth.users import current_admin_user
Expand All @@ -13,7 +11,6 @@
from onyx.db.models import User
from onyx.db.persona import get_persona_by_id
from onyx.db.slack_bot import fetch_slack_bot
from onyx.db.slack_bot import fetch_slack_bot_tokens
from onyx.db.slack_bot import fetch_slack_bots
from onyx.db.slack_bot import insert_slack_bot
from onyx.db.slack_bot import remove_slack_bot
Expand All @@ -27,7 +24,6 @@
from onyx.onyxbot.slack.config import validate_channel_name
from onyx.server.manage.models import SlackBot
from onyx.server.manage.models import SlackBotCreationRequest
from onyx.server.manage.models import SlackChannel
from onyx.server.manage.models import SlackChannelConfig
from onyx.server.manage.models import SlackChannelConfigCreationRequest
from onyx.server.manage.validate_tokens import validate_app_token
Expand Down Expand Up @@ -355,82 +351,3 @@ def list_bot_configs(
SlackChannelConfig.from_model(slack_bot_config_model)
for slack_bot_config_model in slack_bot_config_models
]


@router.get(
"/admin/slack-app/bots/{bot_id}/channels",
)
def get_all_channels_from_slack_api(
bot_id: int,
db_session: Session = Depends(get_session),
_: User | None = Depends(current_admin_user),
) -> list[SlackChannel]:
"""
Returns a list of available slack channels for the slack bot, limited to
SLACK_MAX_RETURNED_CHANNELS.

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)
if not tokens or "bot_token" not in tokens:
raise HTTPException(
status_code=404, detail="Bot token not found for the given bot ID"
)

client = WebClient(token=tokens["bot_token"], timeout=1)
all_channels: list[dict] = []
next_cursor = None

try:
# Use conversations_list to get all channels in the workspace (including ones the bot is not a member of)
while len(all_channels) < SLACK_MAX_RETURNED_CHANNELS:
# Make API call with cursor if we have one
if next_cursor:
response = client.conversations_list(
types="public_channel,private_channel",
exclude_archived=True,
cursor=next_cursor,
limit=SLACK_API_CHANNELS_PER_PAGE,
)
else:
response = client.conversations_list(
types="public_channel,private_channel",
exclude_archived=True,
limit=SLACK_API_CHANNELS_PER_PAGE,
)

# Add channels to our list
if "channels" in response and response["channels"]:
all_channels.extend(response["channels"])

# Check if we need to paginate
if (
"response_metadata" in response
and "next_cursor" in response["response_metadata"]
):
next_cursor = response["response_metadata"]["next_cursor"]
if next_cursor:
continue

# If we get here, no more pages
break

del all_channels[SLACK_MAX_RETURNED_CHANNELS:] # truncate the list

channels = [
SlackChannel(id=channel["id"], name=channel["name"])
for channel in all_channels
]

return channels

except SlackApiError as e:
# Handle rate limiting or other API errors
logger.exception("Error fetching channels from Slack API")
raise HTTPException(
status_code=500,
detail=f"Error fetching channels from Slack API: {str(e)}",
)
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,7 @@ import { TooltipProvider } from "@radix-ui/react-tooltip";
import { SourceIcon } from "@/components/SourceIcon";
import Link from "next/link";
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
import { SearchMultiSelectDropdown } from "@/components/Dropdown";
import { fetchSlackChannels } from "../lib";
import { Badge } from "@/components/ui/badge";
import useSWR from "swr";
import { ThreeDotsLoader } from "@/components/Loading";
import {
Accordion,
AccordionContent,
Expand Down Expand Up @@ -171,48 +167,6 @@ export function SlackChannelConfigFormFields({
);
}, [documentSets]);

const {
data: channelOptions,
error,
isLoading,
} = useSWR(
`/api/manage/admin/slack-app/bots/${slack_bot_id}/channels`,
async () => {
const channels = await fetchSlackChannels(slack_bot_id);
return channels.map((channel: any) => ({
name: channel.name,
value: channel.id,
}));
},
{
shouldRetryOnError: false, // don't spam the Slack API
dedupingInterval: 60000, // Limit re-fetching to once per minute
}
);

// Define the helper text based on the state
const channelHelperText = useMemo(() => {
if (isLoading || error) {
// No helper text needed during loading or if there's an error
// (error message is shown separately)
return null;
}
if (!channelOptions || channelOptions.length === 0) {
return "No channels found. You can type any channel name in directly.";
}

let helpText = `Select a channel from the dropdown list or type any channel name in directly.`;
if (channelOptions.length >= 500) {
return `${helpText} (Retrieved the first ${channelOptions.length} channels.)`;
}

return helpText;
}, [isLoading, error, channelOptions]);

if (isLoading) {
return <ThreeDotsLoader />;
}

return (
<>
<div className="w-full">
Expand Down Expand Up @@ -241,48 +195,12 @@ export function SlackChannelConfigFormFields({
)}
{!isDefault && (
<>
<label
htmlFor="channel_name"
className="block text-text font-medium text-base mb-2"
>
Select A Slack Channel:
</label>{" "}
{error ? (
<div>
<div className="text-red-600 text-sm mb-4">
{error.message || "Unable to fetch Slack channels."}
{" Please enter the channel name manually."}
</div>
<TextFormField
name="channel_name"
label="Channel Name"
placeholder="Enter channel name"
/>
</div>
) : (
<>
<Field name="channel_name">
{({ field, form }: { field: any; form: any }) => (
<SearchMultiSelectDropdown
options={channelOptions || []}
onSelect={(selected) => {
form.setFieldValue("channel_name", selected.name);
}}
initialSearchTerm={field.value}
onSearchTermChange={(term) => {
form.setFieldValue("channel_name", term);
}}
allowCustomValues={true}
/>
)}
</Field>
{channelHelperText && (
<p className="mt-2 text-sm dark:text-neutral-400 text-neutral-600">
{channelHelperText}
</p>
)}
</>
)}
<TextFormField
name="channel_name"
label="Slack Channel Name"
placeholder="Enter channel name (e.g., general, support)"
subtext="Enter the name of the Slack channel (without the # symbol)"
/>
</>
)}
<div className="space-y-2 mt-4">
Expand Down
14 changes: 0 additions & 14 deletions web/src/app/admin/bots/[bot-id]/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,3 @@ export const deleteSlackChannelConfig = async (id: number) => {
export function isPersonaASlackBotPersona(persona: Persona) {
return persona.name.startsWith("__slack_bot_persona__");
}

export const fetchSlackChannels = async (botId: number) => {
return fetch(`/api/manage/admin/slack-app/bots/${botId}/channels`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then((response) => {
if (!response.ok) {
throw new Error("Failed to fetch Slack channels");
}
return response.json();
});
};
Loading