Skip to content

Commit

Permalink
Slackbot is self-serve (#2220)
Browse files Browse the repository at this point in the history
* Enable slack bot by default, graceful error when enabling bot twice

* UI for slack bot toggle

* Mange Slack bot self serve
  • Loading branch information
spolu authored Oct 20, 2023
1 parent f4b8281 commit 1e84eb5
Show file tree
Hide file tree
Showing 7 changed files with 307 additions and 24 deletions.
2 changes: 1 addition & 1 deletion connectors/src/api/bot_enabled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ const _setBotEnabled = async (
return apiError(req, res, {
api_error: {
type: "internal_server_error",
message: `An error occurred while enabling the bot: ${toggleRes.error}`,
message: `An error occurred while toggling the bot: ${toggleRes.error.message}`,
},
status_code: 500,
});
Expand Down
19 changes: 19 additions & 0 deletions connectors/src/connectors/slack/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,13 +549,32 @@ export async function toggleSlackbot(
connectorId: connectorId,
},
});

if (!slackConfig) {
return new Err(
new Error(
`Failed to find a Slack configuration for connector ${connectorId}`
)
);
}

if (botEnabled) {
const otherSlackConfigWithBotEnabled = await SlackConfiguration.findOne({
where: {
slackTeamId: slackConfig.slackTeamId,
botEnabled: true,
},
});

if (otherSlackConfigWithBotEnabled) {
return new Err(
new Error(
"Another Dust workspace has already enabled the slack bot for your Slack workspace."
)
);
}
}

slackConfig.botEnabled = botEnabled;
await slackConfig.save();

Expand Down
11 changes: 10 additions & 1 deletion connectors/src/connectors/slack/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,20 @@ export async function createSlackConnector(
{ transaction: t }
);

const otherSlackConfigurationWithBotEnabled =
await SlackConfiguration.findOne({
where: {
slackTeamId: teamInfo.team.id,
botEnabled: true,
},
transaction: t,
});

await SlackConfiguration.create(
{
slackTeamId: teamInfo.team.id,
connectorId: connector.id,
botEnabled: false,
botEnabled: otherSlackConfigurationWithBotEnabled ? false : true,
},
{ transaction: t }
);
Expand Down
8 changes: 4 additions & 4 deletions front/components/sparkle/Notification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function NotificationsList({
notifications: (NotificationType & { id: string })[];
}) {
return (
<div className="fixed bottom-0 right-0 z-60 w-80">
<div className="fixed bottom-0 right-0 z-60 w-96">
<div className="flex flex-col items-center justify-center gap-4 p-4">
{notifications.map((n) => {
return (
Expand Down Expand Up @@ -100,7 +100,7 @@ export function Notification({ title, description, type }: NotificationType) {
leaveTo="translate-y-16 opacity-0"
>
<div className="flex rounded-md border border-structure-100 bg-structure-0 p-2 shadow-md">
<div>
<div className="pr-2 pt-0.5">
{type === "success" ? (
<Icon
size="sm"
Expand All @@ -118,13 +118,13 @@ export function Notification({ title, description, type }: NotificationType) {
<div className="flex flex-col">
<div
className={classNames(
"text-sm font-semibold capitalize",
"text-md font-semibold",
type === "success" ? "text-success-500" : "text-warning-500"
)}
>
{title || type}
</div>
<div className="text-xs font-normal capitalize text-element-700">
<div className="text-sm font-normal text-element-700">
{description}
</div>
</div>
Expand Down
22 changes: 22 additions & 0 deletions front/lib/swr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { GetRunStatusResponseBody } from "@app/pages/api/w/[wId]/apps/[aId]/runs
import { GetAgentConfigurationsResponseBody } from "@app/pages/api/w/[wId]/assistant/agent_configurations";
import { GetDataSourcesResponseBody } from "@app/pages/api/w/[wId]/data_sources";
import { GetDocumentsResponseBody } from "@app/pages/api/w/[wId]/data_sources/[name]/documents";
import { GetOrPostBotEnabledResponseBody } from "@app/pages/api/w/[wId]/data_sources/[name]/managed/bot_enabled";
import { GetDataSourcePermissionsResponseBody } from "@app/pages/api/w/[wId]/data_sources/[name]/managed/permissions";
import { GetManagedDataSourceDefaultNewResourcePermissionResponseBody } from "@app/pages/api/w/[wId]/data_sources/[name]/managed/permissions/default";
import { GetSlackChannelsLinkedWithAgentResponseBody } from "@app/pages/api/w/[wId]/data_sources/[name]/managed/slack/channels_linked_with_agent";
Expand Down Expand Up @@ -276,6 +277,27 @@ export function useConnectorPermissions({
};
}

export function useConnectorBotEnabled({
owner,
dataSource,
}: {
owner: WorkspaceType;
dataSource: DataSourceType;
}) {
const botEnabledFetcher: Fetcher<GetOrPostBotEnabledResponseBody> = fetcher;

const url = `/api/w/${owner.sId}/data_sources/${dataSource.name}/managed/bot_enabled`;

const { data, error, mutate } = useSWR(url, botEnabledFetcher);

return {
botEnabled: data ? data.botEnabled : null,
isResourcesLoading: !error && !data,
isResourcesError: error,
mutateBotEnabled: mutate,
};
}

export function useConnectorDefaultNewResourcePermission(
owner: WorkspaceType,
dataSource: DataSourceType
Expand Down
147 changes: 147 additions & 0 deletions front/pages/api/w/[wId]/data_sources/[name]/managed/bot_enabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { isLeft } from "fp-ts/lib/Either";
import * as t from "io-ts";
import * as reporter from "io-ts-reporters";
import { NextApiRequest, NextApiResponse } from "next";

import { getDataSource } from "@app/lib/api/data_sources";
import { Authenticator, getSession } from "@app/lib/auth";
import { ConnectorsAPI } from "@app/lib/connectors_api";
import { ReturnedAPIErrorType } from "@app/lib/error";
import { apiError, withLogging } from "@app/logger/withlogging";

export const PostBotEnabledRequestBodySchema = t.type({
botEnabled: t.boolean,
});

export type GetOrPostBotEnabledResponseBody = {
botEnabled: boolean;
};

async function handler(
req: NextApiRequest,
res: NextApiResponse<
GetOrPostBotEnabledResponseBody | ReturnedAPIErrorType | void
>
): Promise<void> {
const session = await getSession(req, res);
const auth = await Authenticator.fromSession(
session,
req.query.wId as string
);

const owner = auth.workspace();
if (!owner) {
return apiError(req, res, {
status_code: 404,
api_error: {
type: "data_source_not_found",
message: "The data source you requested was not found.",
},
});
}

if (!req.query.name || typeof req.query.name !== "string") {
return apiError(req, res, {
status_code: 404,
api_error: {
type: "data_source_not_found",
message: "The data source you requested was not found.",
},
});
}

const dataSource = await getDataSource(auth, req.query.name);
if (!dataSource) {
return apiError(req, res, {
status_code: 404,
api_error: {
type: "data_source_not_found",
message: "The data source you requested was not found.",
},
});
}

if (!dataSource.connectorId) {
return apiError(req, res, {
status_code: 404,
api_error: {
type: "data_source_error",
message: "The data source you requested is not managed.",
},
});
}

switch (req.method) {
case "GET":
const botEnabledRes = await ConnectorsAPI.getBotEnabled(
dataSource.connectorId
);

if (botEnabledRes.isErr()) {
return apiError(req, res, {
status_code: 404,
api_error: {
type: "data_source_error",
message: `Failed to retrieve bot enablement: ${botEnabledRes.error.error.message}`,
},
});
}

res.status(200).json(botEnabledRes.value);
return;

case "POST":
if (!auth.isAdmin()) {
return apiError(req, res, {
status_code: 403,
api_error: {
type: "data_source_auth_error",
message:
"Only the users that are `admins` for the current workspace can edit the (bot) permissions of a data source.",
},
});
}

const bodyValidation = PostBotEnabledRequestBodySchema.decode(req.body);
if (isLeft(bodyValidation)) {
const pathError = reporter.formatValidationErrors(bodyValidation.left);
return apiError(req, res, {
status_code: 400,
api_error: {
type: "invalid_request_error",
message: `Invalid request body: ${pathError}`,
},
});
}

const setBotEnabledRes = await ConnectorsAPI.setBotEnabled(
dataSource.connectorId,
bodyValidation.right.botEnabled
);

if (setBotEnabledRes.isErr()) {
return apiError(req, res, {
status_code: 400,
api_error: {
type: "data_source_error",
message: setBotEnabledRes.error.error.message,
},
});
}

res.status(200).json(setBotEnabledRes.value);
return;

default:
return apiError(req, res, {
status_code: 405,
api_error: {
type: "method_not_supported_error",
message:
"The method passed is not supported, GET or POST is expected.",
},
});
}
}

export default withLogging(handler);
Loading

0 comments on commit 1e84eb5

Please sign in to comment.