fix(bots): simplify event routing status

This commit is contained in:
RockChinQ
2026-08-25 21:18:01 +08:00
parent 6a6a2b865b
commit 68620c4572
7 changed files with 151 additions and 84 deletions
@@ -61,6 +61,11 @@ import {
DialogTitle,
} from '@/components/ui/dialog';
import { Alert, AlertDescription } from '@/components/ui/alert';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import {
DropdownMenu,
DropdownMenuContent,
@@ -1516,8 +1521,8 @@ export default function EventBindingsEditor({
const response = await backendClient.getBotEventRouteStatuses(botId);
setRouteStatuses(response.routes || []);
} catch (error) {
const err = error as { msg?: string };
setRouteStatusError(err.msg || t('bots.routeStatusRefreshFailed'));
console.error('Failed to refresh Bot event route status', error);
setRouteStatusError(t('bots.routeStatusRefreshFailed'));
} finally {
setRouteStatusLoading(false);
}
@@ -1653,18 +1658,18 @@ export default function EventBindingsEditor({
</Alert>
)}
<Alert>
<Info className="h-4 w-4" />
<AlertDescription>
{catchAllRouteIndex >= 0
? t('bots.routeFallbackCatchAll', {
route: t('bots.dryRunRuleIndex', {
index: catchAllRouteIndex + 1,
}),
})
: t('bots.routeFallbackIgnored')}
</AlertDescription>
</Alert>
{catchAllRouteIndex >= 0 && (
<Alert>
<Info className="h-4 w-4" />
<AlertDescription>
{t('bots.routeFallbackCatchAll', {
route: t('bots.dryRunRuleIndex', {
index: catchAllRouteIndex + 1,
}),
})}
</AlertDescription>
</Alert>
)}
{/* enabled section */}
<DndContext
@@ -1778,22 +1783,27 @@ export default function EventBindingsEditor({
agentOptions={agentOptions}
onRouteStatusUpdate={setRouteStatuses}
/>
<Button
type="button"
variant="ghost"
size="sm"
onClick={refreshRouteStatuses}
disabled={!botId || routeStatusLoading}
>
<RefreshCw
className={`h-4 w-4 mr-1 ${routeStatusLoading ? 'animate-spin' : ''}`}
/>
{t('bots.refreshRouteStatus')}
</Button>
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
variant="ghost"
size="icon"
className={`size-8 ${routeStatusError ? 'text-destructive' : 'text-muted-foreground'}`}
aria-label={t('bots.refreshRouteStatus')}
onClick={refreshRouteStatuses}
disabled={!botId || routeStatusLoading}
>
<RefreshCw
className={`h-4 w-4 ${routeStatusLoading ? 'animate-spin' : ''}`}
/>
</Button>
</TooltipTrigger>
<TooltipContent>
{routeStatusError || t('bots.refreshRouteStatus')}
</TooltipContent>
</Tooltip>
</div>
{routeStatusError && (
<p className="text-xs text-destructive">{routeStatusError}</p>
)}
{/* disabled section */}
{disabledBindings.length > 0 && (
+5 -8
View File
@@ -384,8 +384,7 @@ const enUS = {
routingConnectionDescription:
'Bind the pipeline that processes messages for this bot',
eventRouting: 'Event Routing',
eventRoutingDescription:
'Choose which processor handles each event received by this bot. Edit the logic in the corresponding Agent or Pipeline configuration. Pipelines only support message events.',
eventRoutingDescription: 'Choose which processor handles each event.',
eventBindings: 'Event Routes',
addEventBinding: 'Add Route',
addBehavior: 'Add behavior',
@@ -425,18 +424,16 @@ const enUS = {
disable: 'Disable',
enable: 'Enable',
disabledBindings: 'Disabled',
adapterEventsTitle: 'Events this adapter can receive',
adapterEventsDescription:
'{{count}} event types are available. Routes are matched in order, and unmatched events are not sent to a processor.',
adapterEventsTitle: 'Supported events',
adapterEventsDescription: '{{count}} event types',
adapterEventsMore: '{{count}} more',
advancedEventValues: 'Advanced event values',
advancedEventValues: 'View all',
eventGroup: 'Group',
routeConflictTitle: 'Some routes overlap',
routeConflictShadowed:
'{{shadowed}} may never run because {{winner}} handles the same events first.',
routeConflictMore: '{{count}} more route conflicts need attention.',
routeFallbackCatchAll:
'{{route}} is the catch-all route. Routes with higher priority run first.',
routeFallbackCatchAll: '{{route}} is the catch-all route.',
routeFallbackIgnored:
'Events that match no route are ignored. Add a catch-all route only when every event needs an explicit outcome.',
testRoute: 'Test route',
+5 -8
View File
@@ -390,8 +390,7 @@ const jaJP = {
routingConnectionDescription:
'このボットのメッセージを処理するパイプラインを紐付け',
eventRouting: 'イベントルーティング',
eventRoutingDescription:
'このボットが受信した各イベントをどのプロセッサーに渡すかを選択します。対応する Agent または Pipeline の設定で処理ロジックを編集します。Pipeline はメッセージイベントのみ対応します。',
eventRoutingDescription: 'イベントごとの処理先を設定します。',
eventBindings: 'イベントルート',
addEventBinding: 'ルートを追加',
addBehavior: '動作を追加',
@@ -432,18 +431,16 @@ const jaJP = {
disable: '無効化',
enable: '有効化',
disabledBindings: '無効',
adapterEventsTitle: 'このアダプターが受信できるイベント',
adapterEventsDescription:
'{{count}} 種類のイベントを利用できます。ルートは上から順に照合され、未一致のイベントはプロセッサーへ送られません。',
adapterEventsTitle: '対応イベント',
adapterEventsDescription: '{{count}} 種類',
adapterEventsMore: 'ほか {{count}} 件',
advancedEventValues: '高度なイベント値',
advancedEventValues: 'すべて表示',
eventGroup: 'グループ',
routeConflictTitle: '一部のルートが重複しています',
routeConflictShadowed:
'{{winner}} が同じイベントを先に処理するため、{{shadowed}} は実行されない可能性があります。',
routeConflictMore: 'ほか {{count}} 件のルート競合を確認してください。',
routeFallbackCatchAll:
'{{route}} はすべてのイベントを受けるフォールバックです。優先度の高いルートが先に実行されます。',
routeFallbackCatchAll: '{{route}} はフォールバックルートです。',
routeFallbackIgnored:
'どのルートにも一致しないイベントは無視されます。すべてのイベントに明示的な結果が必要な場合のみ、フォールバックを追加してください。',
testRoute: 'ルートをテスト',
+5 -8
View File
@@ -367,8 +367,7 @@ const zhHans = {
routingConnection: '路由与连接',
routingConnectionDescription: '绑定处理此机器人消息的流水线',
eventRouting: '事件路由',
eventRoutingDescription:
'选择此机器人收到不同事件时交给哪个处理器。在对应的 Agent 或 Pipeline 配置中编辑处理逻辑;Pipeline 仅支持消息事件。',
eventRoutingDescription: '设置收到事件后交给哪个处理器。',
eventBindings: '事件路由',
addEventBinding: '添加路由',
addBehavior: '添加行为',
@@ -404,18 +403,16 @@ const zhHans = {
disable: '禁用',
enable: '启用',
disabledBindings: '已禁用',
adapterEventsTitle: '此适配器可接收的事件',
adapterEventsDescription:
'已识别 {{count}} 类事件。路由会按顺序匹配,未命中时不会交给处理器。',
adapterEventsTitle: '支持的事件',
adapterEventsDescription: '共 {{count}} 类',
adapterEventsMore: '另有 {{count}} 类',
advancedEventValues: '高级事件值',
advancedEventValues: '查看全部',
eventGroup: '事件组',
routeConflictTitle: '部分路由存在覆盖冲突',
routeConflictShadowed:
'{{shadowed}} 可能永远不会运行,因为 {{winner}} 会先处理相同事件。',
routeConflictMore: '另有 {{count}} 个路由冲突需要处理。',
routeFallbackCatchAll:
'{{route}} 是全局兜底路由,优先级更高的路由会先运行。',
routeFallbackCatchAll: '{{route}} 是兜底路由。',
routeFallbackIgnored:
'未命中任何路由的事件会被忽略。只有需要为每个事件指定结果时,才添加全局兜底路由。',
testRoute: '测试路由',
+31
View File
@@ -335,6 +335,37 @@ test.describe('frontend CRUD smoke flows', () => {
});
test.describe('bot advanced flows', () => {
test('keeps event routing compact and hides raw status errors', async ({
page,
}) => {
await installLangBotApiMocks(page, { authenticated: true });
await page.route('**/api/v1/platform/bots/*/event-routes/status', (route) =>
route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ code: -1, msg: 'Internal server error' }),
}),
);
await page.goto('/home/bots?id=new');
await selectPlaywrightAdapter(page);
await page.locator('input[name="name"]').fill('Route Status Bot');
await submit(page);
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
await expect(page.getByText('Supported events')).toBeVisible();
await expect(page.getByText('1 event types')).toBeVisible();
await expect(page.getByText('Internal server error')).toHaveCount(0);
await expect(
page.getByText('Events that match no route are ignored.'),
).toHaveCount(0);
await page.getByRole('button', { name: 'Refresh status' }).hover();
await expect(
page.getByText('Failed to refresh route status.'),
).toBeVisible();
});
test('toggles bot enable/disable state', async ({ page }) => {
await installLangBotApiMocks(page, { authenticated: true });