mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-06-11 09:55:59 +00:00
Keep configured IM channels editable
This commit is contained in:
@@ -284,6 +284,12 @@ def _provider_response(
|
|||||||
connection: dict[str, Any] | None = None,
|
connection: dict[str, Any] | None = None,
|
||||||
) -> ChannelProviderResponse:
|
) -> ChannelProviderResponse:
|
||||||
status, unavailable_reason = _provider_status(config, channels_config, provider)
|
status, unavailable_reason = _provider_status(config, channels_config, provider)
|
||||||
|
if connection:
|
||||||
|
connection_status = connection["status"]
|
||||||
|
elif status["configured"] and unavailable_reason is None:
|
||||||
|
connection_status = "connected"
|
||||||
|
else:
|
||||||
|
connection_status = "not_connected"
|
||||||
return ChannelProviderResponse(
|
return ChannelProviderResponse(
|
||||||
provider=provider,
|
provider=provider,
|
||||||
display_name=meta["display_name"],
|
display_name=meta["display_name"],
|
||||||
@@ -292,7 +298,7 @@ def _provider_response(
|
|||||||
connectable=status["enabled"] and status["configured"] and unavailable_reason is None,
|
connectable=status["enabled"] and status["configured"] and unavailable_reason is None,
|
||||||
unavailable_reason=unavailable_reason,
|
unavailable_reason=unavailable_reason,
|
||||||
auth_mode=meta["auth_mode"],
|
auth_mode=meta["auth_mode"],
|
||||||
connection_status=connection["status"] if connection else "not_connected",
|
connection_status=connection_status,
|
||||||
credential_fields=_credential_fields(provider),
|
credential_fields=_credential_fields(provider),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -123,10 +123,12 @@ def test_get_providers_uses_existing_channels_config(tmp_path):
|
|||||||
assert by_provider["telegram"]["auth_mode"] == "deep_link"
|
assert by_provider["telegram"]["auth_mode"] == "deep_link"
|
||||||
assert by_provider["slack"]["configured"] is True
|
assert by_provider["slack"]["configured"] is True
|
||||||
assert by_provider["slack"]["auth_mode"] == "binding_code"
|
assert by_provider["slack"]["auth_mode"] == "binding_code"
|
||||||
|
assert by_provider["slack"]["connection_status"] == "connected"
|
||||||
assert by_provider["discord"]["configured"] is True
|
assert by_provider["discord"]["configured"] is True
|
||||||
assert by_provider["discord"]["auth_mode"] == "binding_code"
|
assert by_provider["discord"]["auth_mode"] == "binding_code"
|
||||||
assert by_provider["feishu"]["configured"] is True
|
assert by_provider["feishu"]["configured"] is True
|
||||||
assert by_provider["feishu"]["auth_mode"] == "binding_code"
|
assert by_provider["feishu"]["auth_mode"] == "binding_code"
|
||||||
|
assert by_provider["feishu"]["connection_status"] == "connected"
|
||||||
assert by_provider["dingtalk"]["configured"] is True
|
assert by_provider["dingtalk"]["configured"] is True
|
||||||
assert by_provider["dingtalk"]["auth_mode"] == "binding_code"
|
assert by_provider["dingtalk"]["auth_mode"] == "binding_code"
|
||||||
assert by_provider["wechat"]["configured"] is True
|
assert by_provider["wechat"]["configured"] is True
|
||||||
@@ -384,6 +386,7 @@ def test_configure_provider_runtime_credentials_enables_connect_without_file_edi
|
|||||||
assert configured["provider"] == "slack"
|
assert configured["provider"] == "slack"
|
||||||
assert configured["configured"] is True
|
assert configured["configured"] is True
|
||||||
assert configured["connectable"] is True
|
assert configured["connectable"] is True
|
||||||
|
assert configured["connection_status"] == "connected"
|
||||||
assert app.state.channels_config["slack"] == {
|
assert app.state.channels_config["slack"] == {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"bot_token": "xoxb-ui",
|
"bot_token": "xoxb-ui",
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ export function ChannelRuntimeConfigDialog({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isEditing = provider.configured;
|
||||||
|
|
||||||
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
|
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
onSubmit(provider, values);
|
onSubmit(provider, values);
|
||||||
@@ -71,7 +73,9 @@ export function ChannelRuntimeConfigDialog({
|
|||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{t.channels.setupTitle(provider.display_name)}
|
{isEditing
|
||||||
|
? t.channels.setupEditTitle(provider.display_name)
|
||||||
|
: t.channels.setupTitle(provider.display_name)}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription>{t.channels.setupDescription}</DialogDescription>
|
<DialogDescription>{t.channels.setupDescription}</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
@@ -118,7 +122,7 @@ export function ChannelRuntimeConfigDialog({
|
|||||||
{submitting ? (
|
{submitting ? (
|
||||||
<LoaderCircleIcon className="animate-spin" />
|
<LoaderCircleIcon className="animate-spin" />
|
||||||
) : null}
|
) : null}
|
||||||
{t.channels.saveAndConnect}
|
{isEditing ? t.channels.saveChanges : t.channels.saveAndConnect}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ function providerCanConnect(provider: ChannelProvider): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function providerCanEditRuntimeConfig(provider: ChannelProvider): boolean {
|
||||||
|
return provider.enabled && (provider.credential_fields?.length ?? 0) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
function getProviderUnavailableReason(
|
function getProviderUnavailableReason(
|
||||||
provider: ChannelProvider,
|
provider: ChannelProvider,
|
||||||
t: ReturnType<typeof useI18n>["t"],
|
t: ReturnType<typeof useI18n>["t"],
|
||||||
@@ -126,6 +130,7 @@ export function WorkspaceChannelsList() {
|
|||||||
<SidebarGroupLabel>{t.sidebar.channels}</SidebarGroupLabel>
|
<SidebarGroupLabel>{t.sidebar.channels}</SidebarGroupLabel>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
{visibleProviders.map((provider) => {
|
{visibleProviders.map((provider) => {
|
||||||
|
const canEditRuntimeConfig = providerCanEditRuntimeConfig(provider);
|
||||||
const isConnected = provider.connection_status === "connected";
|
const isConnected = provider.connection_status === "connected";
|
||||||
const isPending =
|
const isPending =
|
||||||
(connectMutation.isPending &&
|
(connectMutation.isPending &&
|
||||||
@@ -153,10 +158,13 @@ export function WorkspaceChannelsList() {
|
|||||||
"h-8 w-24 px-2 text-xs",
|
"h-8 w-24 px-2 text-xs",
|
||||||
isConnected && "gap-1",
|
isConnected && "gap-1",
|
||||||
)}
|
)}
|
||||||
disabled={isConnected || isPending}
|
disabled={isPending}
|
||||||
title={unavailableReason}
|
title={unavailableReason}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (providerNeedsRuntimeConfig(provider)) {
|
if (
|
||||||
|
providerNeedsRuntimeConfig(provider) ||
|
||||||
|
canEditRuntimeConfig
|
||||||
|
) {
|
||||||
setSetupProvider(provider);
|
setSetupProvider(provider);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -193,16 +201,13 @@ export function WorkspaceChannelsList() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onSubmit={(provider, values) => {
|
onSubmit={(provider, values) => {
|
||||||
const connectWindow =
|
|
||||||
provider.auth_mode === "deep_link" ? prepareConnectWindow() : null;
|
|
||||||
void configureMutation
|
void configureMutation
|
||||||
.mutateAsync({ provider: provider.provider, values })
|
.mutateAsync({ provider: provider.provider, values })
|
||||||
.then((configuredProvider) => {
|
.then(() => {
|
||||||
setSetupProvider(null);
|
setSetupProvider(null);
|
||||||
startConnect(configuredProvider, connectWindow);
|
toast.success(t.channels.connected);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
closeConnectWindow(connectWindow);
|
|
||||||
toast.error(
|
toast.error(
|
||||||
error instanceof Error ? error.message : t.channels.unavailable,
|
error instanceof Error ? error.message : t.channels.unavailable,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -108,6 +108,10 @@ function providerNeedsRuntimeConfig(provider: ChannelProvider): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function providerCanEditRuntimeConfig(provider: ChannelProvider): boolean {
|
||||||
|
return provider.enabled && (provider.credential_fields?.length ?? 0) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
function ChannelProviderItem({
|
function ChannelProviderItem({
|
||||||
provider,
|
provider,
|
||||||
connection,
|
connection,
|
||||||
@@ -120,7 +124,10 @@ function ChannelProviderItem({
|
|||||||
const configureMutation = useConfigureChannelProvider();
|
const configureMutation = useConfigureChannelProvider();
|
||||||
const disconnectMutation = useDisconnectChannelConnection();
|
const disconnectMutation = useDisconnectChannelConnection();
|
||||||
const [setupOpen, setSetupOpen] = useState(false);
|
const [setupOpen, setSetupOpen] = useState(false);
|
||||||
const isConnected = connection?.status === "connected";
|
const isConnected =
|
||||||
|
connection?.status === "connected" ||
|
||||||
|
provider.connection_status === "connected";
|
||||||
|
const canEditRuntimeConfig = providerCanEditRuntimeConfig(provider);
|
||||||
const canConnect =
|
const canConnect =
|
||||||
(provider.connectable ?? (provider.enabled && provider.configured)) &&
|
(provider.connectable ?? (provider.enabled && provider.configured)) &&
|
||||||
!isConnected;
|
!isConnected;
|
||||||
@@ -195,7 +202,25 @@ function ChannelProviderItem({
|
|||||||
</ItemDescription>
|
</ItemDescription>
|
||||||
</ItemContent>
|
</ItemContent>
|
||||||
<ItemActions className="ml-auto">
|
<ItemActions className="ml-auto">
|
||||||
{isConnected && connection ? (
|
{isConnected ? (
|
||||||
|
<>
|
||||||
|
{canEditRuntimeConfig ? (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
disabled={isConnecting}
|
||||||
|
onClick={() => setSetupOpen(true)}
|
||||||
|
>
|
||||||
|
{isConnecting ? (
|
||||||
|
<LoaderCircleIcon className="animate-spin" />
|
||||||
|
) : (
|
||||||
|
<PlugIcon />
|
||||||
|
)}
|
||||||
|
{t.channels.modify}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
{connection ? (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -210,6 +235,8 @@ function ChannelProviderItem({
|
|||||||
)}
|
)}
|
||||||
{t.channels.disconnect}
|
{t.channels.disconnect}
|
||||||
</Button>
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -217,7 +244,10 @@ function ChannelProviderItem({
|
|||||||
disabled={isConnecting}
|
disabled={isConnecting}
|
||||||
title={unavailableReason}
|
title={unavailableReason}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (providerNeedsRuntimeConfig(provider)) {
|
if (
|
||||||
|
providerNeedsRuntimeConfig(provider) ||
|
||||||
|
canEditRuntimeConfig
|
||||||
|
) {
|
||||||
setSetupOpen(true);
|
setSetupOpen(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -248,18 +278,13 @@ function ChannelProviderItem({
|
|||||||
submitting={configureMutation.isPending}
|
submitting={configureMutation.isPending}
|
||||||
onOpenChange={setSetupOpen}
|
onOpenChange={setSetupOpen}
|
||||||
onSubmit={(submitProvider, values) => {
|
onSubmit={(submitProvider, values) => {
|
||||||
const connectWindow =
|
|
||||||
submitProvider.auth_mode === "deep_link"
|
|
||||||
? prepareConnectWindow()
|
|
||||||
: null;
|
|
||||||
void configureMutation
|
void configureMutation
|
||||||
.mutateAsync({ provider: submitProvider.provider, values })
|
.mutateAsync({ provider: submitProvider.provider, values })
|
||||||
.then((configuredProvider) => {
|
.then(() => {
|
||||||
setSetupOpen(false);
|
setSetupOpen(false);
|
||||||
startConnect(configuredProvider, connectWindow);
|
toast.success(t.channels.connected);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
closeConnectWindow(connectWindow);
|
|
||||||
toast.error(
|
toast.error(
|
||||||
error instanceof Error ? error.message : t.channels.unavailable,
|
error instanceof Error ? error.message : t.channels.unavailable,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -259,6 +259,7 @@ export const enUS: Translations = {
|
|||||||
channels: {
|
channels: {
|
||||||
title: "Channels",
|
title: "Channels",
|
||||||
connect: "Connect",
|
connect: "Connect",
|
||||||
|
modify: "Modify",
|
||||||
reconnect: "Reconnect",
|
reconnect: "Reconnect",
|
||||||
disconnect: "Disconnect",
|
disconnect: "Disconnect",
|
||||||
connected: "Connected",
|
connected: "Connected",
|
||||||
@@ -270,9 +271,11 @@ export const enUS: Translations = {
|
|||||||
unavailable: "Channel connections are unavailable right now.",
|
unavailable: "Channel connections are unavailable right now.",
|
||||||
unavailableShort: "Unavailable",
|
unavailableShort: "Unavailable",
|
||||||
setupTitle: (name: string) => `Connect ${name}`,
|
setupTitle: (name: string) => `Connect ${name}`,
|
||||||
|
setupEditTitle: (name: string) => `Modify ${name}`,
|
||||||
setupDescription:
|
setupDescription:
|
||||||
"Enter the values needed by this server process. They are not written to config.yaml.",
|
"Enter the values needed by this server process. They are not written to config.yaml.",
|
||||||
saveAndConnect: "Save and connect",
|
saveAndConnect: "Save and connect",
|
||||||
|
saveChanges: "Save changes",
|
||||||
descriptions: {
|
descriptions: {
|
||||||
telegram: "Telegram direct messages through your DeerFlow bot.",
|
telegram: "Telegram direct messages through your DeerFlow bot.",
|
||||||
slack: "Slack workspace messages and mentions.",
|
slack: "Slack workspace messages and mentions.",
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ export interface Translations {
|
|||||||
channels: {
|
channels: {
|
||||||
title: string;
|
title: string;
|
||||||
connect: string;
|
connect: string;
|
||||||
|
modify: string;
|
||||||
reconnect: string;
|
reconnect: string;
|
||||||
disconnect: string;
|
disconnect: string;
|
||||||
connected: string;
|
connected: string;
|
||||||
@@ -201,8 +202,10 @@ export interface Translations {
|
|||||||
unavailable: string;
|
unavailable: string;
|
||||||
unavailableShort: string;
|
unavailableShort: string;
|
||||||
setupTitle: (name: string) => string;
|
setupTitle: (name: string) => string;
|
||||||
|
setupEditTitle: (name: string) => string;
|
||||||
setupDescription: string;
|
setupDescription: string;
|
||||||
saveAndConnect: string;
|
saveAndConnect: string;
|
||||||
|
saveChanges: string;
|
||||||
descriptions: Record<string, string>;
|
descriptions: Record<string, string>;
|
||||||
connectedAs: (name: string) => string;
|
connectedAs: (name: string) => string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -247,6 +247,7 @@ export const zhCN: Translations = {
|
|||||||
channels: {
|
channels: {
|
||||||
title: "渠道",
|
title: "渠道",
|
||||||
connect: "连接",
|
connect: "连接",
|
||||||
|
modify: "修改",
|
||||||
reconnect: "重新连接",
|
reconnect: "重新连接",
|
||||||
disconnect: "断开连接",
|
disconnect: "断开连接",
|
||||||
connected: "已连接",
|
connected: "已连接",
|
||||||
@@ -258,9 +259,11 @@ export const zhCN: Translations = {
|
|||||||
unavailable: "当前无法使用渠道连接。",
|
unavailable: "当前无法使用渠道连接。",
|
||||||
unavailableShort: "不可用",
|
unavailableShort: "不可用",
|
||||||
setupTitle: (name: string) => `连接 ${name}`,
|
setupTitle: (name: string) => `连接 ${name}`,
|
||||||
|
setupEditTitle: (name: string) => `修改 ${name}`,
|
||||||
setupDescription:
|
setupDescription:
|
||||||
"填写当前服务进程需要的配置值。这些内容不会写入 config.yaml。",
|
"填写当前服务进程需要的配置值。这些内容不会写入 config.yaml。",
|
||||||
saveAndConnect: "保存并连接",
|
saveAndConnect: "保存并连接",
|
||||||
|
saveChanges: "保存修改",
|
||||||
descriptions: {
|
descriptions: {
|
||||||
telegram: "通过 DeerFlow Bot 接收 Telegram 私聊消息。",
|
telegram: "通过 DeerFlow Bot 接收 Telegram 私聊消息。",
|
||||||
slack: "接收 Slack 工作区消息和提及。",
|
slack: "接收 Slack 工作区消息和提及。",
|
||||||
|
|||||||
@@ -37,8 +37,15 @@ function defaultProviders(): MockChannelProvider[] {
|
|||||||
configured: true,
|
configured: true,
|
||||||
connectable: true,
|
connectable: true,
|
||||||
auth_mode: authMode,
|
auth_mode: authMode,
|
||||||
connection_status: "not_connected",
|
connection_status: "connected",
|
||||||
credential_fields: [],
|
credential_fields: [
|
||||||
|
{
|
||||||
|
name: "token",
|
||||||
|
label: "Token",
|
||||||
|
type: "password",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,9 +108,9 @@ test.describe("IM channels", () => {
|
|||||||
await expect(sidebar.getByText("DingTalk")).toBeVisible();
|
await expect(sidebar.getByText("DingTalk")).toBeVisible();
|
||||||
await expect(sidebar.getByText("WeChat")).toBeVisible();
|
await expect(sidebar.getByText("WeChat")).toBeVisible();
|
||||||
await expect(sidebar.getByText("WeCom")).toBeVisible();
|
await expect(sidebar.getByText("WeCom")).toBeVisible();
|
||||||
await expect(sidebar.getByRole("button", { name: "Connect" })).toHaveCount(
|
await expect(
|
||||||
7,
|
sidebar.getByRole("button", { name: "Connected" }),
|
||||||
);
|
).toHaveCount(7);
|
||||||
|
|
||||||
await sidebar.getByRole("button", { name: /Settings and more/ }).click();
|
await sidebar.getByRole("button", { name: /Settings and more/ }).click();
|
||||||
await page.getByRole("menuitem", { name: "Settings" }).click();
|
await page.getByRole("menuitem", { name: "Settings" }).click();
|
||||||
@@ -118,22 +125,14 @@ test.describe("IM channels", () => {
|
|||||||
await expect(page.getByText("WeCom messages")).toBeVisible();
|
await expect(page.getByText("WeCom messages")).toBeVisible();
|
||||||
|
|
||||||
const dialog = page.getByRole("dialog", { name: "Settings" });
|
const dialog = page.getByRole("dialog", { name: "Settings" });
|
||||||
const connectButtons = dialog.getByRole("button", { name: "Connect" });
|
await expect(dialog.getByRole("button", { name: "Modify" })).toHaveCount(7);
|
||||||
await expect(connectButtons).toHaveCount(7);
|
|
||||||
|
|
||||||
await connectButtons.nth(1).click();
|
|
||||||
await expect(page).toHaveURL(/\/workspace\/chats\/new/);
|
|
||||||
await expect(
|
|
||||||
page.getByText("Send /connect abc123 to the DeerFlow Slack bot."),
|
|
||||||
).toBeVisible();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("only enabled providers are shown and setup runs before connect", async ({
|
test("only enabled providers are shown and runtime setup stays editable", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
mockLangGraphAPI(page);
|
mockLangGraphAPI(page);
|
||||||
let slackConfigured = false;
|
let slackConfigured = false;
|
||||||
let connectRequests = 0;
|
|
||||||
let submittedValues: Record<string, string> | undefined;
|
let submittedValues: Record<string, string> | undefined;
|
||||||
|
|
||||||
void page.route("**/api/channels/providers", (route) => {
|
void page.route("**/api/channels/providers", (route) => {
|
||||||
@@ -150,7 +149,9 @@ test.describe("IM channels", () => {
|
|||||||
configured: slackConfigured,
|
configured: slackConfigured,
|
||||||
connectable: slackConfigured,
|
connectable: slackConfigured,
|
||||||
auth_mode: "binding_code",
|
auth_mode: "binding_code",
|
||||||
connection_status: "not_connected",
|
connection_status: slackConfigured
|
||||||
|
? "connected"
|
||||||
|
: "not_connected",
|
||||||
credential_fields: [
|
credential_fields: [
|
||||||
{
|
{
|
||||||
name: "bot_token",
|
name: "bot_token",
|
||||||
@@ -205,27 +206,13 @@ test.describe("IM channels", () => {
|
|||||||
configured: true,
|
configured: true,
|
||||||
connectable: true,
|
connectable: true,
|
||||||
auth_mode: "binding_code",
|
auth_mode: "binding_code",
|
||||||
connection_status: "not_connected",
|
connection_status: "connected",
|
||||||
credential_fields: [],
|
credential_fields: [],
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
void page.route("**/api/channels/slack/connect", (route) => {
|
void page.route("**/api/channels/slack/connect", (route) => route.abort());
|
||||||
connectRequests += 1;
|
|
||||||
return route.fulfill({
|
|
||||||
status: 200,
|
|
||||||
contentType: "application/json",
|
|
||||||
body: JSON.stringify({
|
|
||||||
provider: "slack",
|
|
||||||
mode: "binding_code",
|
|
||||||
url: null,
|
|
||||||
code: "abc123",
|
|
||||||
instruction: "Send /connect abc123 to the DeerFlow Slack bot.",
|
|
||||||
expires_in: 600,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.goto("/workspace/chats/new");
|
await page.goto("/workspace/chats/new");
|
||||||
|
|
||||||
@@ -245,12 +232,15 @@ test.describe("IM channels", () => {
|
|||||||
|
|
||||||
await expect(setupDialog).toBeHidden();
|
await expect(setupDialog).toBeHidden();
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText("Send /connect abc123 to the DeerFlow Slack bot."),
|
sidebar.getByRole("button", { name: "Connected" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await sidebar.getByRole("button", { name: "Connected" }).click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("dialog", { name: "Modify Slack" }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
expect(submittedValues).toEqual({
|
expect(submittedValues).toEqual({
|
||||||
bot_token: "xoxb-ui",
|
bot_token: "xoxb-ui",
|
||||||
app_token: "xapp-ui",
|
app_token: "xapp-ui",
|
||||||
});
|
});
|
||||||
expect(connectRequests).toBe(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user