refactor: unify "Executions" → "Exchanges" terminology

Rename all references from "Execution/executions/execCount" to
"Exchange/exchanges/exchangeCount" to align with Apache Camel's
native Exchange concept. Java class names (CamelExecutionException,
HttpOperationFailedException) are preserved as-is.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-18 15:54:27 +01:00
parent ce3203c842
commit 73f6bdb36c
15 changed files with 169 additions and 171 deletions

View File

@@ -184,7 +184,7 @@ src/
│ ├── ExchangeDetail/ │ ├── ExchangeDetail/
│ └── AgentHealth/ │ └── AgentHealth/
├── mocks/ # Static TypeScript mock data ├── mocks/ # Static TypeScript mock data
│ ├── executions.ts │ ├── exchanges.ts
│ ├── routes.ts │ ├── routes.ts
│ ├── agents.ts │ ├── agents.ts
│ └── metrics.ts │ └── metrics.ts
@@ -361,7 +361,7 @@ All CSS custom properties for both themes. Light values sourced from `mock-v2-li
#### CommandPalette #### CommandPalette
- Full-screen overlay modal triggered by Ctrl+K - Full-screen overlay modal triggered by Ctrl+K
- Search input with scoped filter tags (removable, amber-styled) - Search input with scoped filter tags (removable, amber-styled)
- Category tabs with counts (All, Executions, Routes, Exchanges, Agents) - Category tabs with counts (All, Exchanges, Routes, Agents)
- Grouped results by category with section headers - Grouped results by category with section headers
- Result items: icon + title + badges + meta + timestamp - Result items: icon + title + badges + meta + timestamp
- Inline expandable detail (JSON preview with match highlighting) - Inline expandable detail (JSON preview with match highlighting)
@@ -371,7 +371,7 @@ All CSS custom properties for both themes. Light values sourced from `mock-v2-li
- Props: `open: boolean`, `onClose`, `onSelect: (result: SearchResult) => void`, `data: SearchResult[]` - Props: `open: boolean`, `onClose`, `onSelect: (result: SearchResult) => void`, `data: SearchResult[]`
- `SearchResult` interface: - `SearchResult` interface:
```ts ```ts
type SearchCategory = 'execution' | 'route' | 'exchange' | 'agent' type SearchCategory = 'exchange' | 'route' | 'agent'
interface SearchResult { interface SearchResult {
id: string id: string
category: SearchCategory category: SearchCategory
@@ -451,7 +451,7 @@ All CSS custom properties for both themes. Light values sourced from `mock-v2-li
#### ProcessorTimeline #### ProcessorTimeline
- Gantt-style horizontal bar timeline for exchange processor steps - Gantt-style horizontal bar timeline for exchange processor steps
- Each bar: processor name (left label), colored bar (green=ok, amber=slow, red=fail), duration label (right) - Each bar: processor name (left label), colored bar (green=ok, amber=slow, red=fail), duration label (right)
- Bar width proportional to duration relative to total execution time - Bar width proportional to duration relative to total exchange time
- Clickable bars (select processor) - Clickable bars (select processor)
- Props: `processors: { name: string; type: string; durationMs: number; status: 'ok' | 'slow' | 'fail'; startMs: number }[]`, `totalMs: number`, `onProcessorClick?` - Props: `processors: { name: string; type: string; durationMs: number; status: 'ok' | 'slow' | 'fail'; startMs: number }[]`, `totalMs: number`, `onProcessorClick?`
@@ -518,7 +518,7 @@ Pages are built after the design system layer is complete.
Static TypeScript objects in `src/mocks/` providing realistic data matching the HTML mockups: Static TypeScript objects in `src/mocks/` providing realistic data matching the HTML mockups:
- `executions.ts` — execution rows with status, duration, order IDs, error messages - `exchanges.ts` — exchange rows with status, duration, order IDs, error messages
- `routes.ts` — route definitions with processor lists - `routes.ts` — route definitions with processor lists
- `agents.ts` — agent health data (name, version, status, tps, last-seen) - `agents.ts` — agent health data (name, version, status, tps, last-seen)
- `metrics.ts` — KPI values, chart data points - `metrics.ts` — KPI values, chart data points

View File

@@ -7,8 +7,8 @@ import type { SearchResult } from './types'
const mockData: SearchResult[] = [ const mockData: SearchResult[] = [
{ {
id: '1', id: '1',
category: 'execution', category: 'exchange',
title: 'order-intake execution', title: 'order-intake exchange',
meta: 'cmr-1234 · 142ms', meta: 'cmr-1234 · 142ms',
timestamp: '2s ago', timestamp: '2s ago',
badges: [{ label: 'Completed' }], badges: [{ label: 'Completed' }],
@@ -63,7 +63,7 @@ describe('CommandPalette', () => {
data={mockData} data={mockData}
/>, />,
) )
expect(screen.getByText('order-intake execution')).toBeInTheDocument() expect(screen.getByText('order-intake exchange')).toBeInTheDocument()
expect(screen.getByText('content-based-routing')).toBeInTheDocument() expect(screen.getByText('content-based-routing')).toBeInTheDocument()
}) })
@@ -80,7 +80,7 @@ describe('CommandPalette', () => {
await user.type(screen.getByRole('textbox', { name: 'Search' }), 'routing') await user.type(screen.getByRole('textbox', { name: 'Search' }), 'routing')
// The title is split into fragments by mark highlight, so use a partial text match // The title is split into fragments by mark highlight, so use a partial text match
expect(screen.getByText(/content-based/)).toBeInTheDocument() expect(screen.getByText(/content-based/)).toBeInTheDocument()
expect(screen.queryByText('order-intake execution')).not.toBeInTheDocument() expect(screen.queryByText('order-intake exchange')).not.toBeInTheDocument()
}) })
it('calls onClose when Escape is pressed', async () => { it('calls onClose when Escape is pressed', async () => {
@@ -143,7 +143,7 @@ describe('CommandPalette', () => {
) )
await user.click(screen.getByRole('tab', { name: /Routes/i })) await user.click(screen.getByRole('tab', { name: /Routes/i }))
expect(screen.getByText('content-based-routing')).toBeInTheDocument() expect(screen.getByText('content-based-routing')).toBeInTheDocument()
expect(screen.queryByText('order-intake execution')).not.toBeInTheDocument() expect(screen.queryByText('order-intake exchange')).not.toBeInTheDocument()
}) })
it('shows category tabs with counts', () => { it('shows category tabs with counts', () => {

View File

@@ -16,17 +16,15 @@ interface CommandPaletteProps {
const CATEGORY_LABELS: Record<SearchCategory | 'all', string> = { const CATEGORY_LABELS: Record<SearchCategory | 'all', string> = {
all: 'All', all: 'All',
execution: 'Executions',
route: 'Routes',
exchange: 'Exchanges', exchange: 'Exchanges',
route: 'Routes',
agent: 'Agents', agent: 'Agents',
} }
const ALL_CATEGORIES: Array<SearchCategory | 'all'> = [ const ALL_CATEGORIES: Array<SearchCategory | 'all'> = [
'all', 'all',
'execution',
'route',
'exchange', 'exchange',
'route',
'agent', 'agent',
] ]
@@ -203,7 +201,7 @@ export function CommandPalette({ open, onClose, onSelect, data, onOpen }: Comman
ref={inputRef} ref={inputRef}
type="text" type="text"
className={styles.input} className={styles.input}
placeholder="Search executions, routes, exchanges, agents…" placeholder="Search exchanges, routes, agents…"
value={query} value={query}
onChange={(e) => { onChange={(e) => {
setQuery(e.target.value) setQuery(e.target.value)

View File

@@ -1,6 +1,6 @@
import type { ReactNode } from 'react' import type { ReactNode } from 'react'
export type SearchCategory = 'execution' | 'route' | 'exchange' | 'agent' export type SearchCategory = 'exchange' | 'route' | 'agent'
export interface SearchResult { export interface SearchResult {
id: string id: string

View File

@@ -6,14 +6,14 @@ import { Tabs } from './Tabs'
describe('Tabs', () => { describe('Tabs', () => {
const tabs = [ const tabs = [
{ label: 'All', count: 14, value: 'all' }, { label: 'All', count: 14, value: 'all' },
{ label: 'Executions', count: 8, value: 'executions' }, { label: 'Exchanges', count: 8, value: 'exchanges' },
{ label: 'Routes', count: 3, value: 'routes' }, { label: 'Routes', count: 3, value: 'routes' },
] ]
it('renders all tab labels', () => { it('renders all tab labels', () => {
render(<Tabs tabs={tabs} active="all" onChange={() => {}} />) render(<Tabs tabs={tabs} active="all" onChange={() => {}} />)
expect(screen.getByText('All')).toBeInTheDocument() expect(screen.getByText('All')).toBeInTheDocument()
expect(screen.getByText('Executions')).toBeInTheDocument() expect(screen.getByText('Exchanges')).toBeInTheDocument()
}) })
it('shows count badges', () => { it('shows count badges', () => {

View File

@@ -8,13 +8,13 @@ export interface App {
name: string name: string
agentCount: number agentCount: number
health: 'live' | 'stale' | 'dead' health: 'live' | 'stale' | 'dead'
execCount: number exchangeCount: number
} }
export interface Route { export interface Route {
id: string id: string
name: string name: string
execCount: number exchangeCount: number
} }
export interface Agent { export interface Agent {
@@ -163,7 +163,7 @@ export function Sidebar({
<div className={styles.itemName}>{app.name}</div> <div className={styles.itemName}>{app.name}</div>
<div className={styles.itemMeta}>{app.agentCount} agent{app.agentCount !== 1 ? 's' : ''}</div> <div className={styles.itemMeta}>{app.agentCount} agent{app.agentCount !== 1 ? 's' : ''}</div>
</div> </div>
<span className={styles.itemCount}>{app.execCount.toLocaleString()}</span> <span className={styles.itemCount}>{app.exchangeCount.toLocaleString()}</span>
</div> </div>
))} ))}
</div> </div>
@@ -195,7 +195,7 @@ export function Sidebar({
<div className={styles.itemInfo}> <div className={styles.itemInfo}>
<div className={styles.itemName}>{route.name}</div> <div className={styles.itemName}>{route.name}</div>
</div> </div>
<span className={styles.itemCount}>{route.execCount.toLocaleString()}</span> <span className={styles.itemCount}>{route.exchangeCount.toLocaleString()}</span>
</div> </div>
))} ))}
</div> </div>

View File

@@ -6,7 +6,7 @@ export interface ProcessorData {
startMs: number startMs: number
} }
export interface Execution { export interface Exchange {
id: string id: string
orderId: string orderId: string
customer: string customer: string
@@ -22,7 +22,7 @@ export interface Execution {
processors: ProcessorData[] processors: ProcessorData[]
} }
export const executions: Execution[] = [ export const exchanges: Exchange[] = [
{ {
id: 'E-2026-03-18-00201', id: 'E-2026-03-18-00201',
orderId: 'OP-92184', orderId: 'OP-92184',

View File

@@ -44,7 +44,7 @@ function generateTimeSeries(
// KPI stat cards data // KPI stat cards data
export const kpiMetrics: KpiMetric[] = [ export const kpiMetrics: KpiMetric[] = [
{ {
label: 'Executions (shift)', label: 'Exchanges (shift)',
value: '3,241', value: '3,241',
trend: 'up', trend: 'up',
trendValue: '+12%', trendValue: '+12%',
@@ -147,7 +147,7 @@ export const errorCountSeries: MetricSeries[] = [
export interface RouteMetricRow { export interface RouteMetricRow {
routeId: string routeId: string
routeName: string routeName: string
execCount: number exchangeCount: number
successRate: number successRate: number
avgDurationMs: number avgDurationMs: number
p99DurationMs: number p99DurationMs: number
@@ -159,7 +159,7 @@ export const routeMetrics: RouteMetricRow[] = [
{ {
routeId: 'order-intake', routeId: 'order-intake',
routeName: 'order-intake', routeName: 'order-intake',
execCount: 892, exchangeCount: 892,
successRate: 99.2, successRate: 99.2,
avgDurationMs: 88, avgDurationMs: 88,
p99DurationMs: 142, p99DurationMs: 142,
@@ -169,7 +169,7 @@ export const routeMetrics: RouteMetricRow[] = [
{ {
routeId: 'order-enrichment', routeId: 'order-enrichment',
routeName: 'order-enrichment', routeName: 'order-enrichment',
execCount: 541, exchangeCount: 541,
successRate: 97.6, successRate: 97.6,
avgDurationMs: 156, avgDurationMs: 156,
p99DurationMs: 287, p99DurationMs: 287,
@@ -179,7 +179,7 @@ export const routeMetrics: RouteMetricRow[] = [
{ {
routeId: 'payment-process', routeId: 'payment-process',
routeName: 'payment-process', routeName: 'payment-process',
execCount: 414, exchangeCount: 414,
successRate: 96.1, successRate: 96.1,
avgDurationMs: 234, avgDurationMs: 234,
p99DurationMs: 412, p99DurationMs: 412,
@@ -189,7 +189,7 @@ export const routeMetrics: RouteMetricRow[] = [
{ {
routeId: 'shipment-dispatch', routeId: 'shipment-dispatch',
routeName: 'shipment-dispatch', routeName: 'shipment-dispatch',
execCount: 387, exchangeCount: 387,
successRate: 98.4, successRate: 98.4,
avgDurationMs: 118, avgDurationMs: 118,
p99DurationMs: 248, p99DurationMs: 248,

View File

@@ -3,7 +3,7 @@ export interface RouteDefinition {
name: string name: string
group: string group: string
description: string description: string
execCount: number exchangeCount: number
successRate: number successRate: number
avgDurationMs: number avgDurationMs: number
p99DurationMs: number p99DurationMs: number
@@ -17,7 +17,7 @@ export const routes: RouteDefinition[] = [
name: 'order-intake', name: 'order-intake',
group: 'order-flow', group: 'order-flow',
description: 'Ingests new orders from JMS queue, validates schema, enriches with customer data', description: 'Ingests new orders from JMS queue, validates schema, enriches with customer data',
execCount: 892, exchangeCount: 892,
successRate: 99.2, successRate: 99.2,
avgDurationMs: 88, avgDurationMs: 88,
p99DurationMs: 142, p99DurationMs: 142,
@@ -29,7 +29,7 @@ export const routes: RouteDefinition[] = [
name: 'order-enrichment', name: 'order-enrichment',
group: 'order-flow', group: 'order-flow',
description: 'Enriches order with inventory availability, pricing, and shipment options', description: 'Enriches order with inventory availability, pricing, and shipment options',
execCount: 541, exchangeCount: 541,
successRate: 97.6, successRate: 97.6,
avgDurationMs: 156, avgDurationMs: 156,
p99DurationMs: 287, p99DurationMs: 287,
@@ -41,7 +41,7 @@ export const routes: RouteDefinition[] = [
name: 'payment-process', name: 'payment-process',
group: 'payment-flow', group: 'payment-flow',
description: 'Processes payment through gateway, handles retries and partial failures', description: 'Processes payment through gateway, handles retries and partial failures',
execCount: 414, exchangeCount: 414,
successRate: 96.1, successRate: 96.1,
avgDurationMs: 234, avgDurationMs: 234,
p99DurationMs: 412, p99DurationMs: 412,
@@ -53,7 +53,7 @@ export const routes: RouteDefinition[] = [
name: 'payment-validate', name: 'payment-validate',
group: 'payment-flow', group: 'payment-flow',
description: 'Validates payment details and performs fraud check before processing', description: 'Validates payment details and performs fraud check before processing',
execCount: 498, exchangeCount: 498,
successRate: 99.8, successRate: 99.8,
avgDurationMs: 142, avgDurationMs: 142,
p99DurationMs: 198, p99DurationMs: 198,
@@ -65,7 +65,7 @@ export const routes: RouteDefinition[] = [
name: 'shipment-dispatch', name: 'shipment-dispatch',
group: 'shipment-flow', group: 'shipment-flow',
description: 'Dispatches shipment to carrier after order confirmation', description: 'Dispatches shipment to carrier after order confirmation',
execCount: 387, exchangeCount: 387,
successRate: 98.4, successRate: 98.4,
avgDurationMs: 118, avgDurationMs: 118,
p99DurationMs: 248, p99DurationMs: 248,
@@ -77,7 +77,7 @@ export const routes: RouteDefinition[] = [
name: 'shipment-track', name: 'shipment-track',
group: 'shipment-flow', group: 'shipment-flow',
description: 'Polls carrier APIs for shipment status updates and notifies customers', description: 'Polls carrier APIs for shipment status updates and notifies customers',
execCount: 923, exchangeCount: 923,
successRate: 99.5, successRate: 99.5,
avgDurationMs: 94, avgDurationMs: 94,
p99DurationMs: 167, p99DurationMs: 167,
@@ -89,7 +89,7 @@ export const routes: RouteDefinition[] = [
name: 'notification-dispatch', name: 'notification-dispatch',
group: 'notification-flow', group: 'notification-flow',
description: 'Sends email and SMS notifications to customers for order events', description: 'Sends email and SMS notifications to customers for order events',
execCount: 471, exchangeCount: 471,
successRate: 98.9, successRate: 98.9,
avgDurationMs: 62, avgDurationMs: 62,
p99DurationMs: 124, p99DurationMs: 124,

View File

@@ -22,16 +22,16 @@ import { routes } from '../../mocks/routes'
// ─── Sidebar data (shared) ──────────────────────────────────────────────────── // ─── Sidebar data (shared) ────────────────────────────────────────────────────
const APPS = [ const APPS = [
{ id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, execCount: 1433 }, { id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, exchangeCount: 1433 },
{ id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, execCount: 912 }, { id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, exchangeCount: 912 },
{ id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, execCount: 471 }, { id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, exchangeCount: 471 },
{ id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, execCount: 128 }, { id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, exchangeCount: 128 },
] ]
const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({ const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({
id: r.id, id: r.id,
name: r.name, name: r.name,
execCount: r.execCount, exchangeCount: r.exchangeCount,
})) }))
// ─── Build trend data for each agent ───────────────────────────────────────── // ─── Build trend data for each agent ─────────────────────────────────────────

View File

@@ -24,24 +24,24 @@ import { MonoText } from '../../design-system/primitives/MonoText/MonoText'
import { Badge } from '../../design-system/primitives/Badge/Badge' import { Badge } from '../../design-system/primitives/Badge/Badge'
// Mock data // Mock data
import { executions, type Execution } from '../../mocks/executions' import { exchanges, type Exchange } from '../../mocks/exchanges'
import { routes } from '../../mocks/routes' import { routes } from '../../mocks/routes'
import { agents } from '../../mocks/agents' import { agents } from '../../mocks/agents'
import { kpiMetrics } from '../../mocks/metrics' import { kpiMetrics } from '../../mocks/metrics'
// ─── Sidebar app list (static) ─────────────────────────────────────────────── // ─── Sidebar app list (static) ───────────────────────────────────────────────
const APPS = [ const APPS = [
{ id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, execCount: 1433 }, { id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, exchangeCount: 1433 },
{ id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, execCount: 912 }, { id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, exchangeCount: 912 },
{ id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, execCount: 471 }, { id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, exchangeCount: 471 },
{ id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, execCount: 128 }, { id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, exchangeCount: 128 },
] ]
// ─── Sidebar routes (top 3) ─────────────────────────────────────────────────── // ─── Sidebar routes (top 3) ───────────────────────────────────────────────────
const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({ const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({
id: r.id, id: r.id,
name: r.name, name: r.name,
execCount: r.execCount, exchangeCount: r.exchangeCount,
})) }))
// ─── Helpers ───────────────────────────────────────────────────────────────── // ─── Helpers ─────────────────────────────────────────────────────────────────
@@ -55,7 +55,7 @@ function formatTimestamp(date: Date): string {
return date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) return date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
} }
function statusToVariant(status: Execution['status']): 'success' | 'error' | 'running' | 'warning' { function statusToVariant(status: Exchange['status']): 'success' | 'error' | 'running' | 'warning' {
switch (status) { switch (status) {
case 'completed': return 'success' case 'completed': return 'success'
case 'failed': return 'error' case 'failed': return 'error'
@@ -64,7 +64,7 @@ function statusToVariant(status: Execution['status']): 'success' | 'error' | 'ru
} }
} }
function statusLabel(status: Execution['status']): string { function statusLabel(status: Exchange['status']): string {
switch (status) { switch (status) {
case 'completed': return 'OK' case 'completed': return 'OK'
case 'failed': return 'ERR' case 'failed': return 'ERR'
@@ -74,7 +74,7 @@ function statusLabel(status: Execution['status']): string {
} }
// ─── Table columns ──────────────────────────────────────────────────────────── // ─── Table columns ────────────────────────────────────────────────────────────
const COLUMNS: Column<Execution>[] = [ const COLUMNS: Column<Exchange>[] = [
{ {
key: 'status', key: 'status',
header: 'Status', header: 'Status',
@@ -142,7 +142,7 @@ const COLUMNS: Column<Execution>[] = [
}, },
] ]
function durationClass(ms: number, status: Execution['status']): string { function durationClass(ms: number, status: Exchange['status']): string {
if (status === 'failed') return styles.durBreach if (status === 'failed') return styles.durBreach
if (ms < 100) return styles.durFast if (ms < 100) return styles.durFast
if (ms < 200) return styles.durNormal if (ms < 200) return styles.durNormal
@@ -154,10 +154,10 @@ function durationClass(ms: number, status: Execution['status']): string {
function buildSearchData(): SearchResult[] { function buildSearchData(): SearchResult[] {
const results: SearchResult[] = [] const results: SearchResult[] = []
for (const exec of executions) { for (const exec of exchanges) {
results.push({ results.push({
id: exec.id, id: exec.id,
category: 'execution', category: 'exchange',
title: `${exec.orderId}${exec.route}`, title: `${exec.orderId}${exec.route}`,
badges: [{ label: statusLabel(exec.status), color: statusToVariant(exec.status) }], badges: [{ label: statusLabel(exec.status), color: statusToVariant(exec.status) }],
meta: `${exec.correlationId} · ${formatDuration(exec.durationMs)} · ${exec.customer}`, meta: `${exec.correlationId} · ${formatDuration(exec.durationMs)} · ${exec.customer}`,
@@ -171,7 +171,7 @@ function buildSearchData(): SearchResult[] {
category: 'route', category: 'route',
title: route.name, title: route.name,
badges: [{ label: route.group }], badges: [{ label: route.group }],
meta: `${route.execCount.toLocaleString()} executions · ${route.successRate}% success`, meta: `${route.exchangeCount.toLocaleString()} exchanges · ${route.successRate}% success`,
}) })
} }
@@ -192,11 +192,11 @@ const SEARCH_DATA = buildSearchData()
// ─── Filter options ─────────────────────────────────────────────────────────── // ─── Filter options ───────────────────────────────────────────────────────────
const STATUS_FILTERS = [ const STATUS_FILTERS = [
{ label: 'All', value: 'all', count: executions.length }, { label: 'All', value: 'all', count: exchanges.length },
{ label: 'OK', value: 'completed', count: executions.filter((e) => e.status === 'completed').length, color: 'success' as const }, { label: 'OK', value: 'completed', count: exchanges.filter((e) => e.status === 'completed').length, color: 'success' as const },
{ label: 'Warn', value: 'warning', count: executions.filter((e) => e.status === 'warning').length }, { label: 'Warn', value: 'warning', count: exchanges.filter((e) => e.status === 'warning').length },
{ label: 'Error', value: 'failed', count: executions.filter((e) => e.status === 'failed').length, color: 'error' as const }, { label: 'Error', value: 'failed', count: exchanges.filter((e) => e.status === 'failed').length, color: 'error' as const },
{ label: 'Running', value: 'running', count: executions.filter((e) => e.status === 'running').length, color: 'running' as const }, { label: 'Running', value: 'running', count: exchanges.filter((e) => e.status === 'running').length, color: 'running' as const },
] ]
const SHORTCUTS = [ const SHORTCUTS = [
@@ -213,12 +213,12 @@ export function Dashboard() {
const [search, setSearch] = useState('') const [search, setSearch] = useState('')
const [selectedId, setSelectedId] = useState<string | undefined>() const [selectedId, setSelectedId] = useState<string | undefined>()
const [panelOpen, setPanelOpen] = useState(false) const [panelOpen, setPanelOpen] = useState(false)
const [selectedExecution, setSelectedExecution] = useState<Execution | null>(null) const [selectedExchange, setSelectedExchange] = useState<Exchange | null>(null)
const [paletteOpen, setPaletteOpen] = useState(false) const [paletteOpen, setPaletteOpen] = useState(false)
// Filter executions // Filter exchanges
const filteredExecutions = useMemo(() => { const filteredExchanges = useMemo(() => {
let data = executions let data = exchanges
const statusFilter = activeFilters.find((f) => const statusFilter = activeFilters.find((f) =>
['completed', 'failed', 'running', 'warning', 'all'].includes(f.value), ['completed', 'failed', 'running', 'warning', 'all'].includes(f.value),
@@ -242,20 +242,20 @@ export function Dashboard() {
return data return data
}, [activeFilters, search]) }, [activeFilters, search])
function handleRowClick(row: Execution) { function handleRowClick(row: Exchange) {
setSelectedId(row.id) setSelectedId(row.id)
setSelectedExecution(row) setSelectedExchange(row)
setPanelOpen(true) setPanelOpen(true)
} }
function handleRowAccent(row: Execution): 'error' | 'warning' | undefined { function handleRowAccent(row: Exchange): 'error' | 'warning' | undefined {
if (row.status === 'failed') return 'error' if (row.status === 'failed') return 'error'
if (row.status === 'warning') return 'warning' if (row.status === 'warning') return 'warning'
return undefined return undefined
} }
// Build detail panel tabs for selected execution // Build detail panel tabs for selected exchange
const detailTabs = selectedExecution const detailTabs = selectedExchange
? [ ? [
{ {
label: 'Overview', label: 'Overview',
@@ -264,43 +264,43 @@ export function Dashboard() {
<div className={styles.overviewTab}> <div className={styles.overviewTab}>
<div className={styles.overviewRow}> <div className={styles.overviewRow}>
<span className={styles.overviewLabel}>Order ID</span> <span className={styles.overviewLabel}>Order ID</span>
<MonoText size="sm">{selectedExecution.orderId}</MonoText> <MonoText size="sm">{selectedExchange.orderId}</MonoText>
</div> </div>
<div className={styles.overviewRow}> <div className={styles.overviewRow}>
<span className={styles.overviewLabel}>Route</span> <span className={styles.overviewLabel}>Route</span>
<span>{selectedExecution.route}</span> <span>{selectedExchange.route}</span>
</div> </div>
<div className={styles.overviewRow}> <div className={styles.overviewRow}>
<span className={styles.overviewLabel}>Status</span> <span className={styles.overviewLabel}>Status</span>
<span className={styles.statusCell}> <span className={styles.statusCell}>
<StatusDot variant={statusToVariant(selectedExecution.status)} /> <StatusDot variant={statusToVariant(selectedExchange.status)} />
<span>{statusLabel(selectedExecution.status)}</span> <span>{statusLabel(selectedExchange.status)}</span>
</span> </span>
</div> </div>
<div className={styles.overviewRow}> <div className={styles.overviewRow}>
<span className={styles.overviewLabel}>Duration</span> <span className={styles.overviewLabel}>Duration</span>
<MonoText size="sm">{formatDuration(selectedExecution.durationMs)}</MonoText> <MonoText size="sm">{formatDuration(selectedExchange.durationMs)}</MonoText>
</div> </div>
<div className={styles.overviewRow}> <div className={styles.overviewRow}>
<span className={styles.overviewLabel}>Customer</span> <span className={styles.overviewLabel}>Customer</span>
<MonoText size="sm">{selectedExecution.customer}</MonoText> <MonoText size="sm">{selectedExchange.customer}</MonoText>
</div> </div>
<div className={styles.overviewRow}> <div className={styles.overviewRow}>
<span className={styles.overviewLabel}>Agent</span> <span className={styles.overviewLabel}>Agent</span>
<MonoText size="sm">{selectedExecution.agent}</MonoText> <MonoText size="sm">{selectedExchange.agent}</MonoText>
</div> </div>
<div className={styles.overviewRow}> <div className={styles.overviewRow}>
<span className={styles.overviewLabel}>Correlation ID</span> <span className={styles.overviewLabel}>Correlation ID</span>
<MonoText size="xs">{selectedExecution.correlationId}</MonoText> <MonoText size="xs">{selectedExchange.correlationId}</MonoText>
</div> </div>
<div className={styles.overviewRow}> <div className={styles.overviewRow}>
<span className={styles.overviewLabel}>Timestamp</span> <span className={styles.overviewLabel}>Timestamp</span>
<MonoText size="xs">{selectedExecution.timestamp.toISOString()}</MonoText> <MonoText size="xs">{selectedExchange.timestamp.toISOString()}</MonoText>
</div> </div>
{selectedExecution.errorMessage && ( {selectedExchange.errorMessage && (
<div className={styles.errorBlock}> <div className={styles.errorBlock}>
<div className={styles.errorClass}>{selectedExecution.errorClass}</div> <div className={styles.errorClass}>{selectedExchange.errorClass}</div>
<div className={styles.errorMessage}>{selectedExecution.errorMessage}</div> <div className={styles.errorMessage}>{selectedExchange.errorMessage}</div>
</div> </div>
)} )}
</div> </div>
@@ -312,8 +312,8 @@ export function Dashboard() {
content: ( content: (
<div className={styles.processorsTab}> <div className={styles.processorsTab}>
<ProcessorTimeline <ProcessorTimeline
processors={selectedExecution.processors} processors={selectedExchange.processors}
totalMs={selectedExecution.durationMs} totalMs={selectedExchange.durationMs}
/> />
</div> </div>
), ),
@@ -332,13 +332,13 @@ export function Dashboard() {
value: 'error', value: 'error',
content: ( content: (
<div className={styles.errorTab}> <div className={styles.errorTab}>
{selectedExecution.errorMessage ? ( {selectedExchange.errorMessage ? (
<> <>
<div className={styles.errorClass}>{selectedExecution.errorClass}</div> <div className={styles.errorClass}>{selectedExchange.errorClass}</div>
<pre className={styles.errorPre}>{selectedExecution.errorMessage}</pre> <pre className={styles.errorPre}>{selectedExchange.errorMessage}</pre>
</> </>
) : ( ) : (
<div className={styles.emptyTabMsg}>No error for this execution.</div> <div className={styles.emptyTabMsg}>No error for this exchange.</div>
)} )}
</div> </div>
), ),
@@ -358,11 +358,11 @@ export function Dashboard() {
/> />
} }
detail={ detail={
selectedExecution ? ( selectedExchange ? (
<DetailPanel <DetailPanel
open={panelOpen} open={panelOpen}
onClose={() => setPanelOpen(false)} onClose={() => setPanelOpen(false)}
title={`${selectedExecution.orderId}${selectedExecution.route}`} title={`${selectedExchange.orderId}${selectedExchange.route}`}
tabs={detailTabs} tabs={detailTabs}
/> />
) : undefined ) : undefined
@@ -410,13 +410,13 @@ export function Dashboard() {
className={styles.filterBar} className={styles.filterBar}
/> />
{/* Executions table */} {/* Exchanges table */}
<div className={styles.tableSection}> <div className={styles.tableSection}>
<div className={styles.tableHeader}> <div className={styles.tableHeader}>
<span className={styles.tableTitle}>Recent Executions</span> <span className={styles.tableTitle}>Recent Exchanges</span>
<div className={styles.tableRight}> <div className={styles.tableRight}>
<span className={styles.tableMeta}> <span className={styles.tableMeta}>
{filteredExecutions.length.toLocaleString()} of {executions.length.toLocaleString()} executions {filteredExchanges.length.toLocaleString()} of {exchanges.length.toLocaleString()} exchanges
</span> </span>
<Badge label="LIVE" color="success" /> <Badge label="LIVE" color="success" />
</div> </div>
@@ -424,7 +424,7 @@ export function Dashboard() {
<DataTable <DataTable
columns={COLUMNS} columns={COLUMNS}
data={filteredExecutions} data={filteredExchanges}
onRowClick={handleRowClick} onRowClick={handleRowClick}
selectedId={selectedId} selectedId={selectedId}
sortable sortable

View File

@@ -20,22 +20,22 @@ import { CodeBlock } from '../../design-system/primitives/CodeBlock/CodeBlock'
import { InfoCallout } from '../../design-system/primitives/InfoCallout/InfoCallout' import { InfoCallout } from '../../design-system/primitives/InfoCallout/InfoCallout'
// Mock data // Mock data
import { executions } from '../../mocks/executions' import { exchanges } from '../../mocks/exchanges'
import { routes } from '../../mocks/routes' import { routes } from '../../mocks/routes'
import { agents } from '../../mocks/agents' import { agents } from '../../mocks/agents'
// ─── Sidebar data (shared) ──────────────────────────────────────────────────── // ─── Sidebar data (shared) ────────────────────────────────────────────────────
const APPS = [ const APPS = [
{ id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, execCount: 1433 }, { id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, exchangeCount: 1433 },
{ id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, execCount: 912 }, { id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, exchangeCount: 912 },
{ id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, execCount: 471 }, { id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, exchangeCount: 471 },
{ id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, execCount: 128 }, { id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, exchangeCount: 128 },
] ]
const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({ const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({
id: r.id, id: r.id,
name: r.name, name: r.name,
execCount: r.execCount, exchangeCount: r.exchangeCount,
})) }))
// ─── Helpers ────────────────────────────────────────────────────────────────── // ─── Helpers ──────────────────────────────────────────────────────────────────
@@ -121,7 +121,7 @@ export function ExchangeDetail() {
const navigate = useNavigate() const navigate = useNavigate()
const [activeItem, setActiveItem] = useState('') const [activeItem, setActiveItem] = useState('')
const execution = useMemo(() => executions.find((e) => e.id === id), [id]) const exchange = useMemo(() => exchanges.find((e) => e.id === id), [id])
function handleItemClick(itemId: string) { function handleItemClick(itemId: string) {
setActiveItem(itemId) setActiveItem(itemId)
@@ -130,7 +130,7 @@ export function ExchangeDetail() {
} }
// Not found state // Not found state
if (!execution) { if (!exchange) {
return ( return (
<AppShell <AppShell
sidebar={ sidebar={
@@ -160,8 +160,8 @@ export function ExchangeDetail() {
) )
} }
const statusVariant = statusToVariant(execution.status) const statusVariant = statusToVariant(exchange.status)
const statusLabel = statusToLabel(execution.status) const statusLabel = statusToLabel(exchange.status)
return ( return (
<AppShell <AppShell
@@ -179,8 +179,8 @@ export function ExchangeDetail() {
<TopBar <TopBar
breadcrumb={[ breadcrumb={[
{ label: 'Dashboard', href: '/' }, { label: 'Dashboard', href: '/' },
{ label: execution.route, href: `/routes/${execution.route}` }, { label: exchange.route, href: `/routes/${exchange.route}` },
{ label: execution.id }, { label: exchange.id },
]} ]}
environment="PRODUCTION" environment="PRODUCTION"
shift="Day (06:00-18:00)" shift="Day (06:00-18:00)"
@@ -197,36 +197,36 @@ export function ExchangeDetail() {
<StatusDot variant={statusVariant} /> <StatusDot variant={statusVariant} />
<div> <div>
<div className={styles.exchangeId}> <div className={styles.exchangeId}>
<MonoText size="md">{execution.id}</MonoText> <MonoText size="md">{exchange.id}</MonoText>
<Badge label={statusLabel} color={statusVariant} variant="filled" /> <Badge label={statusLabel} color={statusVariant} variant="filled" />
</div> </div>
<div className={styles.exchangeRoute}> <div className={styles.exchangeRoute}>
Route: <span className={styles.routeLink} onClick={() => navigate(`/routes/${execution.route}`)}>{execution.route}</span> Route: <span className={styles.routeLink} onClick={() => navigate(`/routes/${exchange.route}`)}>{exchange.route}</span>
<span className={styles.headerDivider}>·</span> <span className={styles.headerDivider}>·</span>
Order: <MonoText size="xs">{execution.orderId}</MonoText> Order: <MonoText size="xs">{exchange.orderId}</MonoText>
<span className={styles.headerDivider}>·</span> <span className={styles.headerDivider}>·</span>
Customer: <MonoText size="xs">{execution.customer}</MonoText> Customer: <MonoText size="xs">{exchange.customer}</MonoText>
</div> </div>
</div> </div>
</div> </div>
<div className={styles.headerRight}> <div className={styles.headerRight}>
<div className={styles.headerStat}> <div className={styles.headerStat}>
<div className={styles.headerStatLabel}>Duration</div> <div className={styles.headerStatLabel}>Duration</div>
<div className={styles.headerStatValue}>{formatDuration(execution.durationMs)}</div> <div className={styles.headerStatValue}>{formatDuration(exchange.durationMs)}</div>
</div> </div>
<div className={styles.headerStat}> <div className={styles.headerStat}>
<div className={styles.headerStatLabel}>Agent</div> <div className={styles.headerStatLabel}>Agent</div>
<div className={styles.headerStatValue}>{execution.agent}</div> <div className={styles.headerStatValue}>{exchange.agent}</div>
</div> </div>
<div className={styles.headerStat}> <div className={styles.headerStat}>
<div className={styles.headerStatLabel}>Started</div> <div className={styles.headerStatLabel}>Started</div>
<div className={styles.headerStatValue}> <div className={styles.headerStatValue}>
{execution.timestamp.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' })} {exchange.timestamp.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
</div> </div>
</div> </div>
<div className={styles.headerStat}> <div className={styles.headerStat}>
<div className={styles.headerStatLabel}>Processors</div> <div className={styles.headerStatLabel}>Processors</div>
<div className={styles.headerStatValue}>{execution.processors.length}</div> <div className={styles.headerStatValue}>{exchange.processors.length}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -236,12 +236,12 @@ export function ExchangeDetail() {
<div className={styles.section}> <div className={styles.section}>
<div className={styles.sectionHeader}> <div className={styles.sectionHeader}>
<span className={styles.sectionTitle}>Processor Timeline</span> <span className={styles.sectionTitle}>Processor Timeline</span>
<span className={styles.sectionMeta}>Total: {formatDuration(execution.durationMs)}</span> <span className={styles.sectionMeta}>Total: {formatDuration(exchange.durationMs)}</span>
</div> </div>
<div className={styles.timelineWrap}> <div className={styles.timelineWrap}>
<ProcessorTimeline <ProcessorTimeline
processors={execution.processors} processors={exchange.processors}
totalMs={execution.durationMs} totalMs={exchange.durationMs}
/> />
</div> </div>
</div> </div>
@@ -250,11 +250,11 @@ export function ExchangeDetail() {
<div className={styles.section}> <div className={styles.section}>
<div className={styles.sectionHeader}> <div className={styles.sectionHeader}>
<span className={styles.sectionTitle}>Exchange Inspector</span> <span className={styles.sectionTitle}>Exchange Inspector</span>
<span className={styles.sectionMeta}>{execution.processors.length} processor steps</span> <span className={styles.sectionMeta}>{exchange.processors.length} processor steps</span>
</div> </div>
<div className={styles.inspectorSteps}> <div className={styles.inspectorSteps}>
{execution.processors.map((proc, index) => { {exchange.processors.map((proc, index) => {
const snapshot = generateExchangeSnapshot(proc, execution.orderId, execution.customer, index) const snapshot = generateExchangeSnapshot(proc, exchange.orderId, exchange.customer, index)
const stepStatusClass = const stepStatusClass =
proc.status === 'fail' proc.status === 'fail'
? styles.stepFail ? styles.stepFail
@@ -307,18 +307,18 @@ export function ExchangeDetail() {
</div> </div>
{/* Error block (if failed) */} {/* Error block (if failed) */}
{execution.status === 'failed' && execution.errorMessage && ( {exchange.status === 'failed' && exchange.errorMessage && (
<div className={styles.errorSection}> <div className={styles.errorSection}>
<div className={styles.sectionHeader}> <div className={styles.sectionHeader}>
<span className={styles.sectionTitle}>Error Details</span> <span className={styles.sectionTitle}>Error Details</span>
<Badge label="FAILED" color="error" /> <Badge label="FAILED" color="error" />
</div> </div>
<div className={styles.errorBody}> <div className={styles.errorBody}>
<div className={styles.errorClass}>{execution.errorClass}</div> <div className={styles.errorClass}>{exchange.errorClass}</div>
<pre className={styles.errorMessage}>{execution.errorMessage}</pre> <pre className={styles.errorMessage}>{exchange.errorMessage}</pre>
<div className={styles.errorHint}> <div className={styles.errorHint}>
Failed at processor: <MonoText size="xs"> Failed at processor: <MonoText size="xs">
{execution.processors.find((p) => p.status === 'fail')?.name ?? 'unknown'} {exchange.processors.find((p) => p.status === 'fail')?.name ?? 'unknown'}
</MonoText> </MonoText>
</div> </div>
</div> </div>

View File

@@ -34,16 +34,16 @@ import { agents } from '../../mocks/agents'
// ─── Sidebar data (shared) ──────────────────────────────────────────────────── // ─── Sidebar data (shared) ────────────────────────────────────────────────────
const APPS = [ const APPS = [
{ id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, execCount: 1433 }, { id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, exchangeCount: 1433 },
{ id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, execCount: 912 }, { id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, exchangeCount: 912 },
{ id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, execCount: 471 }, { id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, exchangeCount: 471 },
{ id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, execCount: 128 }, { id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, exchangeCount: 128 },
] ]
const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({ const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({
id: r.id, id: r.id,
name: r.name, name: r.name,
execCount: r.execCount, exchangeCount: r.exchangeCount,
})) }))
// ─── Metrics KPI cards (5 cards per spec) ───────────────────────────────────── // ─── Metrics KPI cards (5 cards per spec) ─────────────────────────────────────
@@ -115,11 +115,11 @@ const ROUTE_COLUMNS: Column<RouteMetricRowWithId>[] = [
), ),
}, },
{ {
key: 'execCount', key: 'exchangeCount',
header: 'Executions', header: 'Exchanges',
sortable: true, sortable: true,
render: (_, row) => ( render: (_, row) => (
<MonoText size="sm">{row.execCount.toLocaleString()}</MonoText> <MonoText size="sm">{row.exchangeCount.toLocaleString()}</MonoText>
), ),
}, },
{ {

View File

@@ -136,7 +136,7 @@
color: var(--text-muted); color: var(--text-muted);
} }
/* Executions table */ /* Exchanges table */
.tableSection { .tableSection {
background: var(--bg-surface); background: var(--bg-surface);
border: 1px solid var(--border-subtle); border: 1px solid var(--border-subtle);

View File

@@ -20,21 +20,21 @@ import { InfoCallout } from '../../design-system/primitives/InfoCallout/InfoCall
// Mock data // Mock data
import { routes } from '../../mocks/routes' import { routes } from '../../mocks/routes'
import { executions, type Execution } from '../../mocks/executions' import { exchanges, type Exchange } from '../../mocks/exchanges'
import { agents } from '../../mocks/agents' import { agents } from '../../mocks/agents'
// ─── Sidebar data (shared) ──────────────────────────────────────────────────── // ─── Sidebar data (shared) ────────────────────────────────────────────────────
const APPS = [ const APPS = [
{ id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, execCount: 1433 }, { id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, exchangeCount: 1433 },
{ id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, execCount: 912 }, { id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, exchangeCount: 912 },
{ id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, execCount: 471 }, { id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, exchangeCount: 471 },
{ id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, execCount: 128 }, { id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, exchangeCount: 128 },
] ]
const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({ const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({
id: r.id, id: r.id,
name: r.name, name: r.name,
execCount: r.execCount, exchangeCount: r.exchangeCount,
})) }))
// ─── Helpers ────────────────────────────────────────────────────────────────── // ─── Helpers ──────────────────────────────────────────────────────────────────
@@ -48,7 +48,7 @@ function formatTimestamp(date: Date): string {
return date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) return date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
} }
function statusToVariant(status: Execution['status']): 'success' | 'error' | 'running' | 'warning' { function statusToVariant(status: Exchange['status']): 'success' | 'error' | 'running' | 'warning' {
switch (status) { switch (status) {
case 'completed': return 'success' case 'completed': return 'success'
case 'failed': return 'error' case 'failed': return 'error'
@@ -57,7 +57,7 @@ function statusToVariant(status: Execution['status']): 'success' | 'error' | 'ru
} }
} }
function statusLabel(status: Execution['status']): string { function statusLabel(status: Exchange['status']): string {
switch (status) { switch (status) {
case 'completed': return 'OK' case 'completed': return 'OK'
case 'failed': return 'ERR' case 'failed': return 'ERR'
@@ -74,7 +74,7 @@ function routeStatusVariant(status: 'healthy' | 'degraded' | 'down'): 'success'
} }
} }
function durationClass(ms: number, status: Execution['status']): string { function durationClass(ms: number, status: Exchange['status']): string {
if (status === 'failed') return styles.durBreach if (status === 'failed') return styles.durBreach
if (ms < 100) return styles.durFast if (ms < 100) return styles.durFast
if (ms < 200) return styles.durNormal if (ms < 200) return styles.durNormal
@@ -82,8 +82,8 @@ function durationClass(ms: number, status: Execution['status']): string {
return styles.durBreach return styles.durBreach
} }
// ─── Columns for executions table ──────────────────────────────────────────── // ─── Columns for exchanges table ────────────────────────────────────────────
const EXEC_COLUMNS: Column<Execution>[] = [ const EXCHANGE_COLUMNS: Column<Exchange>[] = [
{ {
key: 'status', key: 'status',
header: 'Status', header: 'Status',
@@ -97,7 +97,7 @@ const EXEC_COLUMNS: Column<Execution>[] = [
}, },
{ {
key: 'id', key: 'id',
header: 'Execution ID', header: 'Exchange ID',
render: (_, row) => <MonoText size="xs">{row.id}</MonoText>, render: (_, row) => <MonoText size="xs">{row.id}</MonoText>,
}, },
{ {
@@ -146,15 +146,15 @@ export function RouteDetail() {
const [activeItem, setActiveItem] = useState(id ?? '') const [activeItem, setActiveItem] = useState(id ?? '')
const route = useMemo(() => routes.find((r) => r.id === id), [id]) const route = useMemo(() => routes.find((r) => r.id === id), [id])
const routeExecutions = useMemo( const routeExchanges = useMemo(
() => executions.filter((e) => e.route === id), () => exchanges.filter((e) => e.route === id),
[id], [id],
) )
// Error patterns grouped by exception class // Error patterns grouped by exception class
const errorPatterns = useMemo(() => { const errorPatterns = useMemo(() => {
const patterns: Record<string, { count: number; lastMessage: string; lastTime: Date }> = {} const patterns: Record<string, { count: number; lastMessage: string; lastTime: Date }> = {}
for (const exec of routeExecutions) { for (const exec of routeExchanges) {
if (exec.status === 'failed' && exec.errorClass) { if (exec.status === 'failed' && exec.errorClass) {
if (!patterns[exec.errorClass]) { if (!patterns[exec.errorClass]) {
patterns[exec.errorClass] = { patterns[exec.errorClass] = {
@@ -171,26 +171,26 @@ export function RouteDetail() {
} }
} }
return Object.entries(patterns) return Object.entries(patterns)
}, [routeExecutions]) }, [routeExchanges])
// Build aggregate processor timeline from all executions for this route // Build aggregate processor timeline from all exchanges for this route
const aggregateProcessors = useMemo(() => { const aggregateProcessors = useMemo(() => {
if (routeExecutions.length === 0) return [] if (routeExchanges.length === 0) return []
// Use the first execution's processors as the template, with averaged durations // Use the first exchange's processors as the template, with averaged durations
const templateExec = routeExecutions[0] const templateExec = routeExchanges[0]
if (!templateExec) return [] if (!templateExec) return []
return templateExec.processors.map((proc) => { return templateExec.processors.map((proc) => {
const allDurations = routeExecutions const allDurations = routeExchanges
.flatMap((e) => e.processors) .flatMap((e) => e.processors)
.filter((p) => p.name === proc.name) .filter((p) => p.name === proc.name)
.map((p) => p.durationMs) .map((p) => p.durationMs)
const avgDuration = allDurations.length const avgDuration = allDurations.length
? Math.round(allDurations.reduce((a, b) => a + b, 0) / allDurations.length) ? Math.round(allDurations.reduce((a, b) => a + b, 0) / allDurations.length)
: proc.durationMs : proc.durationMs
const hasFailures = routeExecutions.some((e) => const hasFailures = routeExchanges.some((e) =>
e.processors.some((p) => p.name === proc.name && p.status === 'fail'), e.processors.some((p) => p.name === proc.name && p.status === 'fail'),
) )
const hasSlows = routeExecutions.some((e) => const hasSlows = routeExchanges.some((e) =>
e.processors.some((p) => p.name === proc.name && p.status === 'slow'), e.processors.some((p) => p.name === proc.name && p.status === 'slow'),
) )
return { return {
@@ -199,15 +199,15 @@ export function RouteDetail() {
status: hasFailures ? ('fail' as const) : hasSlows ? ('slow' as const) : ('ok' as const), status: hasFailures ? ('fail' as const) : hasSlows ? ('slow' as const) : ('ok' as const),
} }
}) })
}, [routeExecutions]) }, [routeExchanges])
const totalAggregateMs = aggregateProcessors.reduce((sum, p) => sum + p.durationMs, 0) const totalAggregateMs = aggregateProcessors.reduce((sum, p) => sum + p.durationMs, 0)
const inflightCount = routeExecutions.filter((e) => e.status === 'running').length const inflightCount = routeExchanges.filter((e) => e.status === 'running').length
const successCount = routeExecutions.filter((e) => e.status === 'completed').length const successCount = routeExchanges.filter((e) => e.status === 'completed').length
const errorCount = routeExecutions.filter((e) => e.status === 'failed').length const errorCount = routeExchanges.filter((e) => e.status === 'failed').length
const successRate = routeExecutions.length const successRate = routeExchanges.length
? ((successCount / routeExecutions.length) * 100).toFixed(1) ? ((successCount / routeExchanges.length) * 100).toFixed(1)
: '0.0' : '0.0'
function handleItemClick(itemId: string) { function handleItemClick(itemId: string) {
@@ -298,8 +298,8 @@ export function RouteDetail() {
{/* KPI strip */} {/* KPI strip */}
<div className={styles.kpiStrip}> <div className={styles.kpiStrip}>
<div className={styles.kpiCard}> <div className={styles.kpiCard}>
<div className={styles.kpiLabel}>Total Executions</div> <div className={styles.kpiLabel}>Total Exchanges</div>
<div className={styles.kpiValue}>{route.execCount.toLocaleString()}</div> <div className={styles.kpiValue}>{route.exchangeCount.toLocaleString()}</div>
</div> </div>
<div className={styles.kpiCard}> <div className={styles.kpiCard}>
<div className={styles.kpiLabel}>Success Rate</div> <div className={styles.kpiLabel}>Success Rate</div>
@@ -331,7 +331,7 @@ export function RouteDetail() {
<div className={styles.section}> <div className={styles.section}>
<div className={styles.sectionHeader}> <div className={styles.sectionHeader}>
<span className={styles.sectionTitle}>Processor Performance (aggregate avg)</span> <span className={styles.sectionTitle}>Processor Performance (aggregate avg)</span>
<span className={styles.sectionMeta}>Based on {routeExecutions.length} executions</span> <span className={styles.sectionMeta}>Based on {routeExchanges.length} exchanges</span>
</div> </div>
{aggregateProcessors.length > 0 ? ( {aggregateProcessors.length > 0 ? (
<ProcessorTimeline <ProcessorTimeline
@@ -339,17 +339,17 @@ export function RouteDetail() {
totalMs={totalAggregateMs} totalMs={totalAggregateMs}
/> />
) : ( ) : (
<div className={styles.emptyMsg}>No execution data for this route in mock set.</div> <div className={styles.emptyMsg}>No exchange data for this route in mock set.</div>
)} )}
</div> </div>
{/* Recent executions table */} {/* Recent exchanges table */}
<div className={styles.tableSection}> <div className={styles.tableSection}>
<div className={styles.tableHeader}> <div className={styles.tableHeader}>
<span className={styles.tableTitle}>Recent Executions</span> <span className={styles.tableTitle}>Recent Exchanges</span>
<div className={styles.tableRight}> <div className={styles.tableRight}>
<span className={styles.tableMeta}> <span className={styles.tableMeta}>
{routeExecutions.length} executions · {errorCount} errors {routeExchanges.length} exchanges · {errorCount} errors
</span> </span>
<Badge <Badge
label={`${successRate}% ok`} label={`${successRate}% ok`}
@@ -358,8 +358,8 @@ export function RouteDetail() {
</div> </div>
</div> </div>
<DataTable <DataTable
columns={EXEC_COLUMNS} columns={EXCHANGE_COLUMNS}
data={routeExecutions} data={routeExchanges}
sortable sortable
rowAccent={(row) => { rowAccent={(row) => {
if (row.status === 'failed') return 'error' if (row.status === 'failed') return 'error'