mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-27 04:18:35 +02:00
Allow slack channels to be specified (#238)
Adds the capability to specify specific channels to index when using the Slack connector
This commit is contained in:
@@ -46,7 +46,10 @@ def get_channel_info(client: WebClient, channel_id: str) -> ChannelType:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_channels(client: WebClient, exclude_archived: bool = True) -> list[ChannelType]:
|
def get_channels(
|
||||||
|
client: WebClient,
|
||||||
|
exclude_archived: bool = True,
|
||||||
|
) -> list[ChannelType]:
|
||||||
"""Get all channels in the workspace"""
|
"""Get all channels in the workspace"""
|
||||||
channels: list[dict[str, Any]] = []
|
channels: list[dict[str, Any]] = []
|
||||||
for result in _make_slack_api_call(
|
for result in _make_slack_api_call(
|
||||||
@@ -133,9 +136,22 @@ def _default_msg_filter(message: MessageType) -> bool:
|
|||||||
return message.get("subtype", "") in _DISALLOWED_MSG_SUBTYPES
|
return message.get("subtype", "") in _DISALLOWED_MSG_SUBTYPES
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_channels(
|
||||||
|
all_channels: list[dict[str, Any]], channels_to_connect: list[str] | None
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
if channels_to_connect:
|
||||||
|
return [
|
||||||
|
channel
|
||||||
|
for channel in all_channels
|
||||||
|
if channel["name"] in channels_to_connect
|
||||||
|
]
|
||||||
|
return all_channels
|
||||||
|
|
||||||
|
|
||||||
def get_all_docs(
|
def get_all_docs(
|
||||||
client: WebClient,
|
client: WebClient,
|
||||||
workspace: str,
|
workspace: str,
|
||||||
|
channels: list[str] | None = None,
|
||||||
oldest: str | None = None,
|
oldest: str | None = None,
|
||||||
latest: str | None = None,
|
latest: str | None = None,
|
||||||
msg_filter_func: Callable[[MessageType], bool] = _default_msg_filter,
|
msg_filter_func: Callable[[MessageType], bool] = _default_msg_filter,
|
||||||
@@ -143,9 +159,10 @@ def get_all_docs(
|
|||||||
"""Get all documents in the workspace, channel by channel"""
|
"""Get all documents in the workspace, channel by channel"""
|
||||||
user_id_replacer = UserIdReplacer(client=client)
|
user_id_replacer = UserIdReplacer(client=client)
|
||||||
|
|
||||||
channels = get_channels(client)
|
all_channels = get_channels(client)
|
||||||
|
filtered_channels = _filter_channels(all_channels, channels)
|
||||||
|
|
||||||
for channel in channels:
|
for channel in filtered_channels:
|
||||||
channel_docs = 0
|
channel_docs = 0
|
||||||
channel_message_batches = get_channel_messages(
|
channel_message_batches = get_channel_messages(
|
||||||
client=client, channel=channel, oldest=oldest, latest=latest
|
client=client, channel=channel, oldest=oldest, latest=latest
|
||||||
@@ -181,9 +198,14 @@ def get_all_docs(
|
|||||||
|
|
||||||
class SlackLoadConnector(LoadConnector):
|
class SlackLoadConnector(LoadConnector):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, workspace: str, export_path_str: str, batch_size: int = INDEX_BATCH_SIZE
|
self,
|
||||||
|
workspace: str,
|
||||||
|
export_path_str: str,
|
||||||
|
channels: list[str] | None = None,
|
||||||
|
batch_size: int = INDEX_BATCH_SIZE,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.workspace = workspace
|
self.workspace = workspace
|
||||||
|
self.channels = channels
|
||||||
self.export_path_str = export_path_str
|
self.export_path_str = export_path_str
|
||||||
self.batch_size = batch_size
|
self.batch_size = batch_size
|
||||||
|
|
||||||
@@ -245,10 +267,12 @@ class SlackLoadConnector(LoadConnector):
|
|||||||
export_path = Path(self.export_path_str)
|
export_path = Path(self.export_path_str)
|
||||||
|
|
||||||
with open(export_path / "channels.json") as f:
|
with open(export_path / "channels.json") as f:
|
||||||
channels = json.load(f)
|
all_channels = json.load(f)
|
||||||
|
|
||||||
|
filtered_channels = _filter_channels(all_channels, self.channels)
|
||||||
|
|
||||||
document_batch: dict[str, Document] = {}
|
document_batch: dict[str, Document] = {}
|
||||||
for channel_info in channels:
|
for channel_info in filtered_channels:
|
||||||
channel_dir_path = export_path / cast(str, channel_info["name"])
|
channel_dir_path = export_path / cast(str, channel_info["name"])
|
||||||
channel_file_paths = [
|
channel_file_paths = [
|
||||||
channel_dir_path / file_name
|
channel_dir_path / file_name
|
||||||
@@ -275,8 +299,14 @@ class SlackLoadConnector(LoadConnector):
|
|||||||
|
|
||||||
|
|
||||||
class SlackPollConnector(PollConnector):
|
class SlackPollConnector(PollConnector):
|
||||||
def __init__(self, workspace: str, batch_size: int = INDEX_BATCH_SIZE) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
workspace: str,
|
||||||
|
channels: list[str] | None = None,
|
||||||
|
batch_size: int = INDEX_BATCH_SIZE,
|
||||||
|
) -> None:
|
||||||
self.workspace = workspace
|
self.workspace = workspace
|
||||||
|
self.channels = channels
|
||||||
self.batch_size = batch_size
|
self.batch_size = batch_size
|
||||||
self.client: WebClient | None = None
|
self.client: WebClient | None = None
|
||||||
|
|
||||||
@@ -295,6 +325,7 @@ class SlackPollConnector(PollConnector):
|
|||||||
for document in get_all_docs(
|
for document in get_all_docs(
|
||||||
client=self.client,
|
client=self.client,
|
||||||
workspace=self.workspace,
|
workspace=self.workspace,
|
||||||
|
channels=self.channels,
|
||||||
# NOTE: need to impute to `None` instead of using 0.0, since Slack will
|
# NOTE: need to impute to `None` instead of using 0.0, since Slack will
|
||||||
# throw an error if we use 0.0 on an account without infinite data
|
# throw an error if we use 0.0 on an account without infinite data
|
||||||
# retention
|
# retention
|
||||||
|
@@ -14,7 +14,10 @@ import {
|
|||||||
} from "@/lib/types";
|
} from "@/lib/types";
|
||||||
import { deleteCredential, linkCredential } from "@/lib/credential";
|
import { deleteCredential, linkCredential } from "@/lib/credential";
|
||||||
import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
|
import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
|
||||||
import { TextFormField } from "@/components/admin/connectors/Field";
|
import {
|
||||||
|
TextFormField,
|
||||||
|
TextArrayFieldBuilder,
|
||||||
|
} from "@/components/admin/connectors/Field";
|
||||||
import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
|
import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
|
||||||
import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
|
import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
|
||||||
|
|
||||||
@@ -152,6 +155,14 @@ const MainSection = () => {
|
|||||||
getValue: (connector) =>
|
getValue: (connector) =>
|
||||||
connector.connector_specific_config.workspace,
|
connector.connector_specific_config.workspace,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
header: "Channels",
|
||||||
|
key: "channels",
|
||||||
|
getValue: (connector) =>
|
||||||
|
connector.connector_specific_config.channels
|
||||||
|
? connector.connector_specific_config.channels.join(", ")
|
||||||
|
: "All channels",
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
onUpdate={() =>
|
onUpdate={() =>
|
||||||
mutate("/api/manage/admin/connector/indexing-status")
|
mutate("/api/manage/admin/connector/indexing-status")
|
||||||
@@ -170,21 +181,40 @@ const MainSection = () => {
|
|||||||
<div className="border-solid border-gray-600 border rounded-md p-6 mt-4">
|
<div className="border-solid border-gray-600 border rounded-md p-6 mt-4">
|
||||||
<h2 className="font-bold mb-3">Connect to a New Workspace</h2>
|
<h2 className="font-bold mb-3">Connect to a New Workspace</h2>
|
||||||
<ConnectorForm<SlackConfig>
|
<ConnectorForm<SlackConfig>
|
||||||
nameBuilder={(values) => `SlackConnector-${values.workspace}`}
|
nameBuilder={(values) =>
|
||||||
|
values.channels
|
||||||
|
? `SlackConnector-${values.workspace}-${values.channels.join(
|
||||||
|
"_"
|
||||||
|
)}`
|
||||||
|
: `SlackConnector-${values.workspace}`
|
||||||
|
}
|
||||||
source="slack"
|
source="slack"
|
||||||
inputType="poll"
|
inputType="poll"
|
||||||
formBody={
|
formBody={
|
||||||
<>
|
<>
|
||||||
<TextFormField name="workspace" label="Workspace:" />
|
<TextFormField name="workspace" label="Workspace" />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
formBodyBuilder={TextArrayFieldBuilder({
|
||||||
|
name: "channels",
|
||||||
|
label: "Channels:",
|
||||||
|
subtext:
|
||||||
|
"Specify 0 or more channels to index. For example, specifying the channel " +
|
||||||
|
"'support' will cause us to only index all content " +
|
||||||
|
"within the '#support' channel. " +
|
||||||
|
"If no channels are specified, all channels in your workspace will be indexed.",
|
||||||
|
})}
|
||||||
validationSchema={Yup.object().shape({
|
validationSchema={Yup.object().shape({
|
||||||
workspace: Yup.string().required(
|
workspace: Yup.string().required(
|
||||||
"Please enter the workspace to index"
|
"Please enter the workspace to index"
|
||||||
),
|
),
|
||||||
|
channels: Yup.array()
|
||||||
|
.of(Yup.string().required("Channel names must be strings"))
|
||||||
|
.required(),
|
||||||
})}
|
})}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
workspace: "",
|
workspace: "",
|
||||||
|
channels: [],
|
||||||
}}
|
}}
|
||||||
refreshFreq={10 * 60} // 10 minutes
|
refreshFreq={10 * 60} // 10 minutes
|
||||||
onSubmit={async (isSuccess, responseJson) => {
|
onSubmit={async (isSuccess, responseJson) => {
|
||||||
|
@@ -50,7 +50,7 @@ interface BaseProps<T extends Yup.AnyObject> {
|
|||||||
source: ValidSources;
|
source: ValidSources;
|
||||||
inputType: ValidInputTypes;
|
inputType: ValidInputTypes;
|
||||||
credentialId?: number;
|
credentialId?: number;
|
||||||
// If both are specified, uses formBody
|
// If both are specified, will render formBody and then formBodyBuilder
|
||||||
formBody?: JSX.Element | null;
|
formBody?: JSX.Element | null;
|
||||||
formBodyBuilder?: FormBodyBuilder<T>;
|
formBodyBuilder?: FormBodyBuilder<T>;
|
||||||
validationSchema: Yup.ObjectSchema<T>;
|
validationSchema: Yup.ObjectSchema<T>;
|
||||||
@@ -127,7 +127,8 @@ export function ConnectorForm<T extends Yup.AnyObject>({
|
|||||||
>
|
>
|
||||||
{({ isSubmitting, values }) => (
|
{({ isSubmitting, values }) => (
|
||||||
<Form>
|
<Form>
|
||||||
{formBody ? formBody : formBodyBuilder && formBodyBuilder(values)}
|
{formBody && formBody}
|
||||||
|
{formBodyBuilder && formBodyBuilder(values)}
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
@@ -67,6 +67,7 @@ export interface ProductboardConfig {}
|
|||||||
|
|
||||||
export interface SlackConfig {
|
export interface SlackConfig {
|
||||||
workspace: string;
|
workspace: string;
|
||||||
|
channels?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SlabConfig {
|
export interface SlabConfig {
|
||||||
|
Reference in New Issue
Block a user