refactor(bots): remove route execution test

This commit is contained in:
RockChinQ
2026-08-26 14:28:30 +08:00
parent 8b63cc0281
commit 600a173918
18 changed files with 36 additions and 1009 deletions
@@ -112,7 +112,6 @@ import {
Agent,
BotRouteDryRunResult,
BotEventRouteStatus,
BotRouteTestResult,
} from '@/app/infra/entities/api';
import { backendClient } from '@/app/infra/http';
import {
@@ -885,13 +884,11 @@ function RouteDryRunDialog({
bindings,
eventOptions,
agentOptions,
onRouteStatusUpdate,
}: {
botId?: string;
bindings: EventBinding[];
eventOptions: string[];
agentOptions: Agent[];
onRouteStatusUpdate?: (statuses: BotEventRouteStatus[]) => void;
}) {
const { t } = useTranslation();
const firstEvent = eventOptions[0] ?? DEFAULT_EVENTS[0];
@@ -902,12 +899,9 @@ function RouteDryRunDialog({
);
const [advancedPayloadOpen, setAdvancedPayloadOpen] = useState(false);
const [isRunning, setIsRunning] = useState(false);
const [isDispatching, setIsDispatching] = useState(false);
const [payloadError, setPayloadError] = useState<string | null>(null);
const [runError, setRunError] = useState<string | null>(null);
const [result, setResult] = useState<BotRouteDryRunResult | null>(null);
const [dispatchResult, setDispatchResult] =
useState<BotRouteTestResult | null>(null);
useEffect(() => {
if (!eventOptions.includes(eventType)) {
@@ -919,7 +913,6 @@ function RouteDryRunDialog({
setPayloadText(JSON.stringify(samplePayloadForEvent(eventType), null, 2));
setPayloadError(null);
setResult(null);
setDispatchResult(null);
}, [eventType]);
function resolveTargetName(resultTarget?: BotRouteDryRunResult['target']) {
@@ -956,7 +949,6 @@ function RouteDryRunDialog({
async function runDryRun() {
setRunError(null);
setResult(null);
setDispatchResult(null);
const payload = parsePayload();
if (payload === null) return;
@@ -985,44 +977,6 @@ function RouteDryRunDialog({
}
}
async function dispatchTestEvent() {
setRunError(null);
setResult(null);
setDispatchResult(null);
const payload = parsePayload();
if (payload === null) return;
if (!botId) {
setRunError(t('bots.dryRunNeedsSavedBot'));
return;
}
setIsDispatching(true);
try {
const testResult = await backendClient.testBotEventRoute(botId, {
event_type: eventType,
payload,
});
setDispatchResult(testResult);
onRouteStatusUpdate?.(testResult.route_status?.routes || []);
if (!testResult.dispatched) {
setRunError(
localizedFailureReason(
testResult.failure_code,
testResult.reason,
t,
) || t('bots.routeTestFailed'),
);
}
} catch (error) {
const err = error as { msg?: string };
setRunError(err.msg || t('bots.routeTestFailed'));
} finally {
setIsDispatching(false);
}
}
const targetName = result ? resolveTargetName(result.target) : '';
return (
@@ -1206,24 +1160,6 @@ function RouteDryRunDialog({
)}
</div>
)}
{dispatchResult?.dispatched && (
<Alert>
<CheckCircle2 className="h-4 w-4" />
<AlertDescription>
{t('bots.routeTestDispatched', {
count: dispatchResult.suppressed_outputs?.length || 0,
})}
</AlertDescription>
</Alert>
)}
<Alert className="border-amber-200 bg-amber-50/60 px-3 py-2 text-amber-900 dark:border-amber-900/50 dark:bg-amber-950/20 dark:text-amber-200">
<AlertCircle className="h-4 w-4" />
<AlertDescription className="text-xs">
{t('bots.routeTestSideEffectWarning')}
</AlertDescription>
</Alert>
</div>
<DialogFooter>
@@ -1234,25 +1170,10 @@ function RouteDryRunDialog({
>
{t('common.close')}
</Button>
<Button
type="button"
onClick={runDryRun}
disabled={isRunning || isDispatching}
>
<Button type="button" onClick={runDryRun} disabled={isRunning}>
<Play className="h-4 w-4 mr-1" />
{isRunning ? t('bots.dryRunRunning') : t('bots.dryRunAction')}
</Button>
<Button
type="button"
variant="outline"
onClick={dispatchTestEvent}
disabled={isRunning || isDispatching}
>
<Activity className="h-4 w-4 mr-1" />
{isDispatching
? t('bots.routeTestRunning')
: t('bots.routeTestAction')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
@@ -1878,7 +1799,6 @@ export default function EventBindingsEditor({
bindings={bindings}
eventOptions={dryRunEventOptions}
agentOptions={agentOptions}
onRouteStatusUpdate={setRouteStatuses}
/>
<Tooltip>
<TooltipTrigger asChild>
-16
View File
@@ -275,11 +275,6 @@ export interface BotRouteDryRunRequest {
event_bindings?: EventBinding[];
}
export interface BotRouteTestRequest {
event_type: string;
payload?: Record<string, unknown>;
}
export interface BotRouteDryRunTarget {
target_type: EventBinding['target_type'];
target_uuid?: string | null;
@@ -334,17 +329,6 @@ export interface BotEventRouteStatusResponse {
stale_routes: BotEventRouteStatus[];
}
export interface BotRouteTestResult {
dispatched: boolean;
event_type: string;
status?: BotEventRouteStatus['last_status'];
binding_id?: string | null;
failure_code?: string | null;
reason?: string | null;
suppressed_outputs: Array<Record<string, unknown>>;
route_status: BotEventRouteStatusResponse;
}
export interface ApiRespKnowledgeBases {
bases: KnowledgeBase[];
}
-12
View File
@@ -61,8 +61,6 @@ import {
ApiRespSkill,
BotRouteDryRunRequest,
BotRouteDryRunResult,
BotRouteTestRequest,
BotRouteTestResult,
BotEventRouteStatusResponse,
} from '@/app/infra/entities/api';
import { Plugin } from '@/app/infra/entities/plugin';
@@ -512,16 +510,6 @@ export class BackendClient extends BaseHttpClient {
return this.get(`/api/v1/platform/bots/${botId}/event-routes/status`);
}
public testBotEventRoute(
botId: string,
request: BotRouteTestRequest,
): Promise<BotRouteTestResult> {
return this.post(
`/api/v1/platform/bots/${botId}/event-routes/test`,
request,
);
}
public deleteBot(uuid: string): Promise<object> {
return this.delete(`/api/v1/platform/bots/${uuid}`);
}
+7 -18
View File
@@ -450,7 +450,7 @@ const enUS = {
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',
testRoute: 'Check route',
refreshRouteStatus: 'Refresh status',
routeStatusIdle: 'No run yet',
routeStatusRefreshFailed: 'Failed to refresh route status.',
@@ -460,7 +460,6 @@ const enUS = {
discarded: 'Discarded',
failed: 'Failed',
not_matched: 'Not matched',
test_started: 'Testing',
},
routeStatusDetail: {
matched: 'This route matched the event.',
@@ -468,7 +467,6 @@ const enUS = {
discarded: 'The event was intentionally discarded.',
failed: 'The route could not finish.',
not_matched: 'No configured route matched the event.',
test_started: 'The saved route is running.',
},
routeFailure: {
binding_disabled: 'This route is disabled.',
@@ -479,21 +477,12 @@ const enUS = {
processor_incompatible:
'The selected processor cannot handle this event.',
processor_not_found: 'The selected processor is unavailable.',
bot_runtime_unavailable:
'The bot is not running. Check its platform settings and enable it before running a full test.',
runner_failed: 'The Agent runner failed while processing the event.',
delivery_failed: 'The processor finished, but delivery failed.',
},
routeTestAction: 'Run full test',
routeTestRunning: 'Running…',
routeTestFailed: 'Failed to run the saved route. Try again later.',
routeTestDispatched:
'The saved route ran successfully. {{count}} platform actions were blocked.',
routeTestSideEffectWarning:
'The processor and tools will run, but replies will not be sent to messaging platforms.',
dryRunTitle: 'Test route',
dryRunTitle: 'Check event route',
dryRunDescription:
'Choose an event to see which processor handles it, or run a test.',
'Choose an event to see which route and processor it matches.',
dryRunEventType: 'Event type',
dryRunSampleReady: 'Sample event is ready',
dryRunSampleDescription:
@@ -504,10 +493,10 @@ const enUS = {
dryRunPayloadHint: 'Use this to test message and conversation conditions.',
dryRunPayloadJsonError: 'Enter valid JSON.',
dryRunPayloadObjectError: 'Payload must be a JSON object.',
dryRunNeedsSavedBot: 'Save the bot before testing routes.',
dryRunFailed: 'Route test failed. Try again later.',
dryRunAction: 'Preview match',
dryRunRunning: 'Testing…',
dryRunNeedsSavedBot: 'Save the bot before checking routes.',
dryRunFailed: 'Failed to check the route. Try again later.',
dryRunAction: 'View match',
dryRunRunning: 'Checking…',
dryRunMatched: 'Route matched',
dryRunNotMatched: 'No route matched',
dryRunTarget: 'Target processor',
+7 -20
View File
@@ -457,7 +457,7 @@ const jaJP = {
routeFallbackCatchAll: '{{route}} はフォールバックルートです。',
routeFallbackIgnored:
'どのルートにも一致しないイベントは無視されます。すべてのイベントに明示的な結果が必要な場合のみ、フォールバックを追加してください。',
testRoute: 'ルートをテスト',
testRoute: 'ルートを確認',
refreshRouteStatus: '状態を更新',
routeStatusIdle: '実行記録なし',
routeStatusRefreshFailed: 'ルート状態の更新に失敗しました。',
@@ -467,7 +467,6 @@ const jaJP = {
discarded: '破棄済み',
failed: '失敗',
not_matched: '未一致',
test_started: 'テスト中',
},
routeStatusDetail: {
matched: 'このルートがイベントに一致しました。',
@@ -475,7 +474,6 @@ const jaJP = {
discarded: '設定に従ってイベントを破棄しました。',
failed: 'ルートを完了できませんでした。',
not_matched: '設定済みルートに一致しませんでした。',
test_started: '保存済みルートを実行しています。',
},
routeFailure: {
binding_disabled: 'このルートは無効です。',
@@ -486,22 +484,11 @@ const jaJP = {
processor_incompatible:
'選択したプロセッサーはこのイベントを処理できません。',
processor_not_found: '選択したプロセッサーを利用できません。',
bot_runtime_unavailable:
'ボットが実行されていません。プラットフォーム設定を確認してボットを有効にした後、完全テストを実行してください。',
runner_failed: 'Agent Runner がイベント処理中に失敗しました。',
delivery_failed: '処理は完了しましたが、結果の配信に失敗しました。',
},
routeTestAction: '完全テストを実行',
routeTestRunning: '実行中…',
routeTestFailed:
'保存済みルートの実行に失敗しました。後でもう一度お試しください。',
routeTestDispatched:
'保存済みルートを実行しました。{{count}} 件のプラットフォーム操作を抑制しました。',
routeTestSideEffectWarning:
'プロセッサーとツールは実行されますが、返信はメッセージプラットフォームへ送信されません。',
dryRunTitle: 'ルートをテスト',
dryRunDescription:
'イベントを選び、処理先を確認するか、テストを実行します。',
dryRunTitle: 'イベントルートを確認',
dryRunDescription: 'イベントを選び、一致するルートと処理先を確認します。',
dryRunEventType: 'イベントタイプ',
dryRunSampleReady: 'サンプルイベントを準備しました',
dryRunSampleDescription:
@@ -513,10 +500,10 @@ const jaJP = {
dryRunPayloadJsonError: '有効な JSON を入力してください。',
dryRunPayloadObjectError:
'ペイロードは JSON オブジェクトである必要があります。',
dryRunNeedsSavedBot: 'ルートをテストする前にボットを保存してください。',
dryRunFailed: 'ルートテストに失敗しました。後でもう一度お試しください。',
dryRunAction: '一致を確認',
dryRunRunning: 'テスト中…',
dryRunNeedsSavedBot: 'ルートを確認する前にボットを保存してください。',
dryRunFailed: 'ルートを確認できませんでした。後でもう一度お試しください。',
dryRunAction: '一致結果を確認',
dryRunRunning: '確認中…',
dryRunMatched: 'ルートに一致しました',
dryRunNotMatched: '一致するルートはありません',
dryRunTarget: '対象プロセッサー',
+7 -17
View File
@@ -429,7 +429,7 @@ const zhHans = {
routeFallbackCatchAll: '{{route}} 是兜底路由。',
routeFallbackIgnored:
'未命中任何路由的事件会被忽略。只有需要为每个事件指定结果时,才添加全局兜底路由。',
testRoute: '测试路由',
testRoute: '检查路由',
refreshRouteStatus: '刷新状态',
routeStatusIdle: '暂无运行记录',
routeStatusRefreshFailed: '刷新路由状态失败。',
@@ -439,7 +439,6 @@ const zhHans = {
discarded: '已丢弃',
failed: '失败',
not_matched: '未命中',
test_started: '测试中',
},
routeStatusDetail: {
matched: '此路由已命中事件。',
@@ -447,7 +446,6 @@ const zhHans = {
discarded: '此事件已按路由配置丢弃。',
failed: '此路由未能完成。',
not_matched: '没有已配置路由命中此事件。',
test_started: '正在运行已保存路由。',
},
routeFailure: {
binding_disabled: '此路由已禁用。',
@@ -457,19 +455,11 @@ const zhHans = {
route_not_found: '没有路由命中此事件。',
processor_incompatible: '所选处理器无法处理此事件。',
processor_not_found: '所选处理器不可用。',
bot_runtime_unavailable:
'机器人尚未运行。请检查平台配置并启用机器人,再运行完整测试。',
runner_failed: 'Agent Runner 处理事件时失败。',
delivery_failed: '处理器已完成,但结果投递失败。',
},
routeTestAction: '运行完整测试',
routeTestRunning: '运行中…',
routeTestFailed: '运行已保存路由失败,请稍后重试。',
routeTestDispatched: '已保存路由运行成功,{{count}} 个平台操作已被阻止。',
routeTestSideEffectWarning:
'测试会运行处理器和工具,但不会把回复发送到微信、QQ 等聊天平台。',
dryRunTitle: '测试路由',
dryRunDescription: '选择事件,查看它会交给哪个处理器,或运行一次测试。',
dryRunTitle: '检查事件路由',
dryRunDescription: '选择事件,查看它会匹配哪条路由、交给哪个处理器。',
dryRunEventType: '事件类型',
dryRunSampleReady: '示例事件已准备好',
dryRunSampleDescription:
@@ -480,10 +470,10 @@ const zhHans = {
dryRunPayloadHint: '用于测试消息内容、会话类型等条件。',
dryRunPayloadJsonError: '请输入合法 JSON。',
dryRunPayloadObjectError: '载荷必须是 JSON 对象。',
dryRunNeedsSavedBot: '请先保存机器人后再测试路由。',
dryRunFailed: '路由测试失败,请稍后重试。',
dryRunAction: '预览匹配',
dryRunRunning: '测试中…',
dryRunNeedsSavedBot: '请先保存机器人后再检查路由。',
dryRunFailed: '无法检查路由,请稍后重试。',
dryRunAction: '查看匹配结果',
dryRunRunning: '检查中…',
dryRunMatched: '已命中路由',
dryRunNotMatched: '未命中路由',
dryRunTarget: '目标处理器',
+6 -45
View File
@@ -389,29 +389,6 @@ test.describe('bot advanced flows', () => {
}),
}),
);
await page.route('**/api/v1/platform/bots/*/event-routes/test', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: 0,
msg: 'ok',
data: {
dispatched: false,
event_type: 'message.received',
failure_code: 'bot_runtime_unavailable',
reason: 'Bot runtime is unavailable',
suppressed_outputs: [],
route_status: {
routes: [],
unmatched_events: [],
stale_routes: [],
},
},
}),
}),
);
await page.goto('/home/bots?id=new');
await selectPlaywrightAdapter(page);
await page.locator('input[name="name"]').fill('Route Status Bot');
@@ -454,14 +431,14 @@ test.describe('bot advanced flows', () => {
page.getByText('Failed to refresh route status.'),
).toBeVisible();
await page.getByRole('button', { name: 'Test route' }).click();
await page.getByRole('button', { name: 'Check route' }).click();
const routeDialog = page.getByRole('dialog');
await expect(
routeDialog.getByText('Test route', { exact: true }),
routeDialog.getByText('Check event route', { exact: true }),
).toBeVisible();
await expect(
routeDialog.getByText(
'Choose an event to see which processor handles it, or run a test.',
'Choose an event to see which route and processor it matches.',
),
).toBeVisible();
await expect(routeDialog.getByText('Sample event is ready')).toHaveCount(0);
@@ -469,11 +446,11 @@ test.describe('bot advanced flows', () => {
routeDialog.getByRole('button', { name: 'Test data' }),
).toBeVisible();
await expect(
routeDialog.getByRole('button', { name: 'Preview match' }),
routeDialog.getByRole('button', { name: 'View match' }),
).toBeVisible();
await expect(
routeDialog.getByRole('button', { name: 'Run full test' }),
).toBeVisible();
).toHaveCount(0);
const routeEventPicker = routeDialog.getByRole('combobox', {
name: 'Event type',
});
@@ -494,25 +471,9 @@ test.describe('bot advanced flows', () => {
);
await page.keyboard.press('Escape');
await routeDialog.getByRole('button', { name: 'Preview match' }).click();
await routeDialog.getByRole('button', { name: 'View match' }).click();
await expect(routeDialog.getByText('Matched route')).toBeVisible();
await routeDialog.getByRole('button', { name: 'Run full test' }).click();
await expect(routeDialog.getByText('Matched route')).toHaveCount(0);
await expect(
routeDialog.getByText(
'The bot is not running. Check its platform settings and enable it before running a full test.',
),
).toBeVisible();
await expect(routeDialog.getByText('Internal server error')).toHaveCount(0);
await routeDialog.getByRole('button', { name: 'Preview match' }).click();
await expect(
routeDialog.getByText(
'The bot is not running. Check its platform settings and enable it before running a full test.',
),
).toHaveCount(0);
await expect(routeDialog.getByText('Matched route')).toBeVisible();
const dialogBox = await routeDialog.boundingBox();
expect(dialogBox).not.toBeNull();
expect(dialogBox!.height).toBeLessThan(500);