mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-27 20:57:13 +00:00
refactor(bots): remove route execution test
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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: '対象プロセッサー',
|
||||
|
||||
@@ -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: '目标处理器',
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user