mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-26 20:27:13 +00:00
refactor(events): group event selection options
This commit is contained in:
@@ -17,6 +17,11 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/components/ui/popover';
|
} from '@/components/ui/popover';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import {
|
||||||
|
eventGroupLabel,
|
||||||
|
eventNamespaces,
|
||||||
|
groupEventPatterns,
|
||||||
|
} from '@/app/home/components/event-patterns/event-pattern-groups';
|
||||||
|
|
||||||
const FALLBACK_EVENTS = ['message.received'];
|
const FALLBACK_EVENTS = ['message.received'];
|
||||||
|
|
||||||
@@ -26,19 +31,6 @@ interface AgentEventPatternPickerProps {
|
|||||||
onChange: (patterns: string[]) => void;
|
onChange: (patterns: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function eventNamespaces(events: string[]) {
|
|
||||||
const counts = new Map<string, number>();
|
|
||||||
events.forEach((event) => {
|
|
||||||
if (event === '*' || event.endsWith('.*')) return;
|
|
||||||
const namespace = event.split('.')[0];
|
|
||||||
if (namespace) counts.set(namespace, (counts.get(namespace) ?? 0) + 1);
|
|
||||||
});
|
|
||||||
return Array.from(counts.entries())
|
|
||||||
.filter(([, count]) => count >= 2)
|
|
||||||
.map(([namespace]) => `${namespace}.*`)
|
|
||||||
.sort();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AgentEventPatternPicker({
|
export default function AgentEventPatternPicker({
|
||||||
events,
|
events,
|
||||||
value,
|
value,
|
||||||
@@ -67,6 +59,7 @@ export default function AgentEventPatternPicker({
|
|||||||
).sort();
|
).sort();
|
||||||
return ['*', ...namespaces, ...concreteEvents];
|
return ['*', ...namespaces, ...concreteEvents];
|
||||||
}, [events, selectedPatterns]);
|
}, [events, selectedPatterns]);
|
||||||
|
const optionGroups = useMemo(() => groupEventPatterns(options), [options]);
|
||||||
|
|
||||||
function eventLabel(pattern: string) {
|
function eventLabel(pattern: string) {
|
||||||
if (pattern === '*') return t('bots.eventWildcard');
|
if (pattern === '*') return t('bots.eventWildcard');
|
||||||
@@ -154,39 +147,44 @@ export default function AgentEventPatternPicker({
|
|||||||
<CommandInput placeholder={t('agents.searchEvents')} />
|
<CommandInput placeholder={t('agents.searchEvents')} />
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>{t('agents.noEventsFound')}</CommandEmpty>
|
<CommandEmpty>{t('agents.noEventsFound')}</CommandEmpty>
|
||||||
<CommandGroup heading={t('agents.supportedEvents')}>
|
{optionGroups.map((group) => (
|
||||||
{options.map((pattern) => {
|
<CommandGroup
|
||||||
const selected = selectedPatterns.includes(pattern);
|
key={group.namespace}
|
||||||
return (
|
heading={eventGroupLabel(group.namespace, t)}
|
||||||
<CommandItem
|
>
|
||||||
key={pattern}
|
{group.patterns.map((pattern) => {
|
||||||
value={`${eventLabel(pattern)} ${pattern}`}
|
const selected = selectedPatterns.includes(pattern);
|
||||||
onSelect={() => togglePattern(pattern)}
|
return (
|
||||||
className="items-start gap-2 py-2"
|
<CommandItem
|
||||||
>
|
key={pattern}
|
||||||
<Check
|
value={`${eventLabel(pattern)} ${pattern}`}
|
||||||
className={cn(
|
onSelect={() => togglePattern(pattern)}
|
||||||
'mt-0.5 size-4 shrink-0',
|
className="items-start gap-2 py-2"
|
||||||
selected ? 'opacity-100' : 'opacity-0',
|
>
|
||||||
)}
|
<Check
|
||||||
/>
|
className={cn(
|
||||||
<span className="min-w-0 flex-1">
|
'mt-0.5 size-4 shrink-0',
|
||||||
<span className="flex min-w-0 items-center gap-2">
|
selected ? 'opacity-100' : 'opacity-0',
|
||||||
<span className="truncate font-medium">
|
)}
|
||||||
{eventLabel(pattern)}
|
/>
|
||||||
|
<span className="min-w-0 flex-1">
|
||||||
|
<span className="flex min-w-0 items-center gap-2">
|
||||||
|
<span className="truncate font-medium">
|
||||||
|
{eventLabel(pattern)}
|
||||||
|
</span>
|
||||||
|
<code className="shrink-0 text-[10px] text-muted-foreground">
|
||||||
|
{pattern}
|
||||||
|
</code>
|
||||||
|
</span>
|
||||||
|
<span className="mt-0.5 block text-xs text-muted-foreground">
|
||||||
|
{eventDescription(pattern)}
|
||||||
</span>
|
</span>
|
||||||
<code className="shrink-0 text-[10px] text-muted-foreground">
|
|
||||||
{pattern}
|
|
||||||
</code>
|
|
||||||
</span>
|
</span>
|
||||||
<span className="mt-0.5 block text-xs text-muted-foreground">
|
</CommandItem>
|
||||||
{eventDescription(pattern)}
|
);
|
||||||
</span>
|
})}
|
||||||
</span>
|
</CommandGroup>
|
||||||
</CommandItem>
|
))}
|
||||||
);
|
|
||||||
})}
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</Command>
|
</Command>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ import {
|
|||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
@@ -102,6 +104,11 @@ import {
|
|||||||
BotRouteTestResult,
|
BotRouteTestResult,
|
||||||
} from '@/app/infra/entities/api';
|
} from '@/app/infra/entities/api';
|
||||||
import { backendClient } from '@/app/infra/http';
|
import { backendClient } from '@/app/infra/http';
|
||||||
|
import {
|
||||||
|
eventGroupLabel,
|
||||||
|
eventNamespaces,
|
||||||
|
groupEventPatterns,
|
||||||
|
} from '@/app/home/components/event-patterns/event-pattern-groups';
|
||||||
|
|
||||||
export const PIPELINE_DISCARD = '__discard__';
|
export const PIPELINE_DISCARD = '__discard__';
|
||||||
|
|
||||||
@@ -302,20 +309,6 @@ function agentSupportsEventPattern(agent: Agent, pattern: string) {
|
|||||||
return patterns.some((p) => eventPatternCovers(p, pattern));
|
return patterns.some((p) => eventPatternCovers(p, pattern));
|
||||||
}
|
}
|
||||||
|
|
||||||
function eventNamespaces(events: string[]) {
|
|
||||||
// Only surface a `ns.*` wildcard when the namespace actually has 2+
|
|
||||||
// concrete events — otherwise the wildcard is redundant with the single event.
|
|
||||||
const counts = new Map<string, number>();
|
|
||||||
events.forEach((e) => {
|
|
||||||
const n = e.split('.')[0];
|
|
||||||
if (n) counts.set(n, (counts.get(n) ?? 0) + 1);
|
|
||||||
});
|
|
||||||
return Array.from(counts.entries())
|
|
||||||
.filter(([, c]) => c >= 2)
|
|
||||||
.map(([n]) => `${n}.*`)
|
|
||||||
.sort();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Localized label for an event pattern. Concrete events look up
|
// Localized label for an event pattern. Concrete events look up
|
||||||
// `bots.eventNames.<event_with_underscores>`, falling back to the raw
|
// `bots.eventNames.<event_with_underscores>`, falling back to the raw
|
||||||
// string when no translation exists (e.g. custom/unknown events).
|
// string when no translation exists (e.g. custom/unknown events).
|
||||||
@@ -771,7 +764,8 @@ function AdapterCapabilitySummary({
|
|||||||
const [advancedOpen, setAdvancedOpen] = useState(false);
|
const [advancedOpen, setAdvancedOpen] = useState(false);
|
||||||
const concreteEvents =
|
const concreteEvents =
|
||||||
supportedEvents.length > 0 ? supportedEvents : DEFAULT_EVENTS;
|
supportedEvents.length > 0 ? supportedEvents : DEFAULT_EVENTS;
|
||||||
const previewEvents = concreteEvents.slice(0, 4);
|
const concreteEventGroups = groupEventPatterns(concreteEvents);
|
||||||
|
const optionGroups = groupEventPatterns(eventOptions);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border bg-muted/20 p-3">
|
<div className="rounded-lg border bg-muted/20 p-3">
|
||||||
@@ -793,20 +787,22 @@ function AdapterCapabilitySummary({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
{previewEvents.map((event) => (
|
{concreteEventGroups.slice(0, 4).map((group) => (
|
||||||
<Badge
|
<Badge
|
||||||
key={event}
|
key={group.namespace}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="max-w-full rounded-md px-2 py-0.5 font-normal"
|
className="max-w-full rounded-md px-2 py-0.5 font-normal"
|
||||||
title={event}
|
|
||||||
>
|
>
|
||||||
<span className="truncate">{eventLabel(event, t)}</span>
|
<span className="truncate">
|
||||||
|
{eventGroupLabel(group.namespace, t)} ·{' '}
|
||||||
|
{group.patterns.length}
|
||||||
|
</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
{concreteEvents.length > previewEvents.length && (
|
{concreteEventGroups.length > 4 && (
|
||||||
<Badge variant="outline" className="rounded-md px-2 py-0.5">
|
<Badge variant="outline" className="rounded-md px-2 py-0.5">
|
||||||
{t('bots.adapterEventsMore', {
|
{t('bots.adapterEventsMore', {
|
||||||
count: concreteEvents.length - previewEvents.length,
|
count: concreteEventGroups.length - 4,
|
||||||
})}
|
})}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
@@ -828,25 +824,40 @@ function AdapterCapabilitySummary({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{advancedOpen && (
|
{advancedOpen && (
|
||||||
<div className="mt-3 grid gap-2 border-t pt-3 sm:grid-cols-2">
|
<div className="mt-3 space-y-4 border-t pt-3">
|
||||||
{eventOptions.map((event) => (
|
{optionGroups.map((group) => (
|
||||||
<div key={event} className="min-w-0 rounded-md bg-background p-2">
|
<div key={group.namespace} className="space-y-2">
|
||||||
<div className="flex min-w-0 items-center justify-between gap-2">
|
<p className="text-xs font-medium text-muted-foreground">
|
||||||
<span className="truncate text-xs font-medium">
|
{eventGroupLabel(group.namespace, t)}
|
||||||
{eventLabel(event, t)}
|
|
||||||
</span>
|
|
||||||
{event.endsWith('.*') && (
|
|
||||||
<Badge variant="outline" className="shrink-0 text-[10px]">
|
|
||||||
{t('bots.eventGroup')}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<p className="mt-1 line-clamp-2 text-[11px] leading-snug text-muted-foreground">
|
|
||||||
{eventDescription(event, t)}
|
|
||||||
</p>
|
</p>
|
||||||
<code className="mt-1 block truncate text-[11px] text-muted-foreground">
|
<div className="grid gap-2 sm:grid-cols-2">
|
||||||
{event}
|
{group.patterns.map((event) => (
|
||||||
</code>
|
<div
|
||||||
|
key={event}
|
||||||
|
className="min-w-0 rounded-md bg-background p-2"
|
||||||
|
>
|
||||||
|
<div className="flex min-w-0 items-center justify-between gap-2">
|
||||||
|
<span className="truncate text-xs font-medium">
|
||||||
|
{eventLabel(event, t)}
|
||||||
|
</span>
|
||||||
|
{event.endsWith('.*') && (
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="shrink-0 text-[10px]"
|
||||||
|
>
|
||||||
|
{t('bots.eventGroup')}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="mt-1 line-clamp-2 text-[11px] leading-snug text-muted-foreground">
|
||||||
|
{eventDescription(event, t)}
|
||||||
|
</p>
|
||||||
|
<code className="mt-1 block truncate text-[11px] text-muted-foreground">
|
||||||
|
{event}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -1032,10 +1043,17 @@ function RouteDryRunDialog({
|
|||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{eventOptions.map((event) => (
|
{groupEventPatterns(eventOptions).map((group) => (
|
||||||
<SelectItem key={event} value={event}>
|
<SelectGroup key={group.namespace}>
|
||||||
{eventLabel(event, t)}
|
<SelectLabel>
|
||||||
</SelectItem>
|
{eventGroupLabel(group.namespace, t)}
|
||||||
|
</SelectLabel>
|
||||||
|
{group.patterns.map((event) => (
|
||||||
|
<SelectItem key={event} value={event}>
|
||||||
|
{eventLabel(event, t)}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
@@ -1304,19 +1322,21 @@ function BindingCardContent({
|
|||||||
)}
|
)}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{eventOptions.map((event) => {
|
{groupEventPatterns(eventOptions).map((group) => (
|
||||||
const label = eventLabel(event, t);
|
<SelectGroup key={group.namespace}>
|
||||||
return (
|
<SelectLabel>{eventGroupLabel(group.namespace, t)}</SelectLabel>
|
||||||
<SelectItem key={event} value={event}>
|
{group.patterns.map((event) => (
|
||||||
<span className="flex flex-col">
|
<SelectItem key={event} value={event}>
|
||||||
<span>{label}</span>
|
<span className="flex flex-col">
|
||||||
<span className="text-[11px] text-muted-foreground">
|
<span>{eventLabel(event, t)}</span>
|
||||||
{eventDescription(event, t)}
|
<span className="text-[11px] text-muted-foreground">
|
||||||
|
{eventDescription(event, t)}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</SelectItem>
|
||||||
</SelectItem>
|
))}
|
||||||
);
|
</SelectGroup>
|
||||||
})}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import type { TFunction } from 'i18next';
|
||||||
|
|
||||||
|
export interface EventPatternGroup {
|
||||||
|
namespace: string;
|
||||||
|
patterns: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function eventPatternNamespace(pattern: string) {
|
||||||
|
if (pattern === '*') return '*';
|
||||||
|
return pattern.split('.')[0] || pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function eventNamespaces(events: string[]) {
|
||||||
|
const counts = new Map<string, number>();
|
||||||
|
events.forEach((event) => {
|
||||||
|
if (event === '*' || event.endsWith('.*')) return;
|
||||||
|
const namespace = eventPatternNamespace(event);
|
||||||
|
counts.set(namespace, (counts.get(namespace) ?? 0) + 1);
|
||||||
|
});
|
||||||
|
return Array.from(counts.entries())
|
||||||
|
.filter(([, count]) => count >= 2)
|
||||||
|
.map(([namespace]) => `${namespace}.*`)
|
||||||
|
.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function groupEventPatterns(patterns: string[]): EventPatternGroup[] {
|
||||||
|
const groups = new Map<string, string[]>();
|
||||||
|
patterns.forEach((pattern) => {
|
||||||
|
const namespace = eventPatternNamespace(pattern);
|
||||||
|
const group = groups.get(namespace) ?? [];
|
||||||
|
if (!group.includes(pattern)) group.push(pattern);
|
||||||
|
groups.set(namespace, group);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(groups.entries())
|
||||||
|
.sort(([left], [right]) => {
|
||||||
|
if (left === '*') return -1;
|
||||||
|
if (right === '*') return 1;
|
||||||
|
return left.localeCompare(right);
|
||||||
|
})
|
||||||
|
.map(([namespace, groupPatterns]) => ({
|
||||||
|
namespace,
|
||||||
|
patterns: groupPatterns.sort((left, right) => {
|
||||||
|
const leftWildcard = left.endsWith('.*');
|
||||||
|
const rightWildcard = right.endsWith('.*');
|
||||||
|
if (leftWildcard !== rightWildcard) return leftWildcard ? -1 : 1;
|
||||||
|
return left.localeCompare(right);
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function eventGroupLabel(namespace: string, t: TFunction) {
|
||||||
|
if (namespace === '*') return t('bots.eventWildcard');
|
||||||
|
const key = `bots.eventGroupNames.${namespace}`;
|
||||||
|
const label = t(key);
|
||||||
|
return label === key ? namespace : label;
|
||||||
|
}
|
||||||
@@ -433,6 +433,14 @@ const enUS = {
|
|||||||
adapterEventsMore: '{{count}} more',
|
adapterEventsMore: '{{count}} more',
|
||||||
advancedEventValues: 'View all',
|
advancedEventValues: 'View all',
|
||||||
eventGroup: 'Group',
|
eventGroup: 'Group',
|
||||||
|
eventGroupNames: {
|
||||||
|
bot: 'Bot status',
|
||||||
|
feedback: 'Feedback',
|
||||||
|
friend: 'Friends',
|
||||||
|
group: 'Groups',
|
||||||
|
message: 'Messages',
|
||||||
|
platform: 'Platform',
|
||||||
|
},
|
||||||
routeConflictTitle: 'Some routes overlap',
|
routeConflictTitle: 'Some routes overlap',
|
||||||
routeConflictShadowed:
|
routeConflictShadowed:
|
||||||
'{{shadowed}} may never run because {{winner}} handles the same events first.',
|
'{{shadowed}} may never run because {{winner}} handles the same events first.',
|
||||||
|
|||||||
@@ -440,6 +440,14 @@ const jaJP = {
|
|||||||
adapterEventsMore: 'ほか {{count}} 件',
|
adapterEventsMore: 'ほか {{count}} 件',
|
||||||
advancedEventValues: 'すべて表示',
|
advancedEventValues: 'すべて表示',
|
||||||
eventGroup: 'グループ',
|
eventGroup: 'グループ',
|
||||||
|
eventGroupNames: {
|
||||||
|
bot: 'ボットの状態',
|
||||||
|
feedback: 'フィードバック',
|
||||||
|
friend: '友だち',
|
||||||
|
group: 'グループ',
|
||||||
|
message: 'メッセージ',
|
||||||
|
platform: 'プラットフォーム',
|
||||||
|
},
|
||||||
routeConflictTitle: '一部のルートが重複しています',
|
routeConflictTitle: '一部のルートが重複しています',
|
||||||
routeConflictShadowed:
|
routeConflictShadowed:
|
||||||
'{{winner}} が同じイベントを先に処理するため、{{shadowed}} は実行されない可能性があります。',
|
'{{winner}} が同じイベントを先に処理するため、{{shadowed}} は実行されない可能性があります。',
|
||||||
|
|||||||
@@ -412,6 +412,14 @@ const zhHans = {
|
|||||||
adapterEventsMore: '另有 {{count}} 类',
|
adapterEventsMore: '另有 {{count}} 类',
|
||||||
advancedEventValues: '查看全部',
|
advancedEventValues: '查看全部',
|
||||||
eventGroup: '事件组',
|
eventGroup: '事件组',
|
||||||
|
eventGroupNames: {
|
||||||
|
bot: '机器人状态',
|
||||||
|
feedback: '反馈',
|
||||||
|
friend: '好友',
|
||||||
|
group: '群组',
|
||||||
|
message: '消息',
|
||||||
|
platform: '平台',
|
||||||
|
},
|
||||||
routeConflictTitle: '部分路由存在覆盖冲突',
|
routeConflictTitle: '部分路由存在覆盖冲突',
|
||||||
routeConflictShadowed:
|
routeConflictShadowed:
|
||||||
'{{shadowed}} 可能永远不会运行,因为 {{winner}} 会先处理相同事件。',
|
'{{shadowed}} 可能永远不会运行,因为 {{winner}} 会先处理相同事件。',
|
||||||
|
|||||||
@@ -353,7 +353,10 @@ test.describe('bot advanced flows', () => {
|
|||||||
test('keeps event routing compact and hides raw status errors', async ({
|
test('keeps event routing compact and hides raw status errors', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
await installLangBotApiMocks(page, { authenticated: true });
|
await installLangBotApiMocks(page, {
|
||||||
|
authenticated: true,
|
||||||
|
withAdapterEvents: true,
|
||||||
|
});
|
||||||
await page.route('**/api/v1/platform/bots/*/event-routes/status', (route) =>
|
await page.route('**/api/v1/platform/bots/*/event-routes/status', (route) =>
|
||||||
route.fulfill({
|
route.fulfill({
|
||||||
status: 500,
|
status: 500,
|
||||||
@@ -416,7 +419,9 @@ test.describe('bot advanced flows', () => {
|
|||||||
|
|
||||||
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
|
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
|
||||||
await expect(page.getByText('Supported events')).toBeVisible();
|
await expect(page.getByText('Supported events')).toBeVisible();
|
||||||
await expect(page.getByText('1 event types')).toBeVisible();
|
await expect(page.getByText('5 event types')).toBeVisible();
|
||||||
|
await expect(page.getByText('Messages · 2')).toBeVisible();
|
||||||
|
await expect(page.getByText('Groups · 2')).toBeVisible();
|
||||||
await expect(page.getByText('Internal server error')).toHaveCount(0);
|
await expect(page.getByText('Internal server error')).toHaveCount(0);
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText('Events that match no route are ignored.'),
|
page.getByText('Events that match no route are ignored.'),
|
||||||
@@ -436,6 +441,14 @@ test.describe('bot advanced flows', () => {
|
|||||||
dangerBox!.y - (routingBox!.y + routingBox!.height),
|
dangerBox!.y - (routingBox!.y + routingBox!.height),
|
||||||
).toBeGreaterThanOrEqual(20);
|
).toBeGreaterThanOrEqual(20);
|
||||||
|
|
||||||
|
await routingCard.getByRole('button', { name: 'View all' }).click();
|
||||||
|
await expect(
|
||||||
|
routingCard.getByText('Messages', { exact: true }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
routingCard.getByText('Groups', { exact: true }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Refresh status' }).hover();
|
await page.getByRole('button', { name: 'Refresh status' }).hover();
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText('Failed to refresh route status.'),
|
page.getByText('Failed to refresh route status.'),
|
||||||
@@ -461,6 +474,14 @@ test.describe('bot advanced flows', () => {
|
|||||||
await expect(
|
await expect(
|
||||||
routeDialog.getByRole('button', { name: 'Run full test' }),
|
routeDialog.getByRole('button', { name: 'Run full test' }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
await routeDialog.getByRole('combobox').first().click();
|
||||||
|
await expect(
|
||||||
|
page.getByText('Messages', { exact: true }).last(),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByText('Groups', { exact: true }).last(),
|
||||||
|
).toBeVisible();
|
||||||
|
await page.keyboard.press('Escape');
|
||||||
|
|
||||||
await routeDialog.getByRole('button', { name: 'Preview match' }).click();
|
await routeDialog.getByRole('button', { name: 'Preview match' }).click();
|
||||||
await expect(routeDialog.getByText('Matched route')).toBeVisible();
|
await expect(routeDialog.getByText('Matched route')).toBeVisible();
|
||||||
|
|||||||
@@ -106,6 +106,8 @@ test.describe('processor detail workbench', () => {
|
|||||||
await expect(
|
await expect(
|
||||||
page.getByRole('option').filter({ hasText: 'message.received' }),
|
page.getByRole('option').filter({ hasText: 'message.received' }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole('group', { name: 'Messages' })).toHaveCount(1);
|
||||||
|
await expect(page.getByRole('group', { name: 'Groups' })).toHaveCount(1);
|
||||||
await page.keyboard.press('Escape');
|
await page.keyboard.press('Escape');
|
||||||
|
|
||||||
await flow.getByRole('tab').nth(1).click();
|
await flow.getByRole('tab').nth(1).click();
|
||||||
|
|||||||
Reference in New Issue
Block a user