From 638b86864916e4ac647f876de2793464e791c198 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Thu, 9 Apr 2026 08:28:32 +0200 Subject: [PATCH] refactor: extract duplicated utility functions into shared format-utils Consolidate formatDuration, statusToVariant, statusLabel, formatTimestamp, toRouteNodeType, and durationClass from 5 page/component files into one shared utils module. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../composites/RouteFlow/RouteFlow.tsx | 7 +-- src/mocks/searchData.tsx | 29 +--------- src/pages/Dashboard/Dashboard.tsx | 47 ++-------------- src/pages/ExchangeDetail/ExchangeDetail.tsx | 28 +--------- src/pages/RouteDetail/RouteDetail.tsx | 41 ++------------ src/utils/format-utils.ts | 55 +++++++++++++++++++ 6 files changed, 68 insertions(+), 139 deletions(-) create mode 100644 src/utils/format-utils.ts diff --git a/src/design-system/composites/RouteFlow/RouteFlow.tsx b/src/design-system/composites/RouteFlow/RouteFlow.tsx index cbd9ce1..89e93c7 100644 --- a/src/design-system/composites/RouteFlow/RouteFlow.tsx +++ b/src/design-system/composites/RouteFlow/RouteFlow.tsx @@ -2,6 +2,7 @@ import type { ReactNode } from 'react' import { Play, Cog, Square, Diamond, AlertTriangle, EllipsisVertical } from 'lucide-react' import styles from './RouteFlow.module.css' import { Dropdown } from '../Dropdown/Dropdown' +import { formatDuration } from '../../../utils/format-utils' export interface NodeBadge { label: string @@ -42,12 +43,6 @@ interface RouteFlowProps { className?: string } -function formatDuration(ms: number): string { - if (ms >= 60_000) return `${(ms / 1000).toFixed(0)}s` - if (ms >= 1000) return `${(ms / 1000).toFixed(2)}s` - return `${ms}ms` -} - function durationClass(ms: number, status: string): string { if (status === 'fail') return styles.durBreach if (ms < 50) return styles.durFast diff --git a/src/mocks/searchData.tsx b/src/mocks/searchData.tsx index e46bb70..f66e8ef 100644 --- a/src/mocks/searchData.tsx +++ b/src/mocks/searchData.tsx @@ -3,34 +3,7 @@ import { exchanges, type Exchange } from './exchanges' import { routes } from './routes' import { agents } from './agents' import { SIDEBAR_APPS, buildRouteToAppMap, type SidebarApp } from './sidebar' - -function formatDuration(ms: number): string { - if (ms >= 60_000) return `${(ms / 1000).toFixed(0)}s` - if (ms >= 1000) return `${(ms / 1000).toFixed(2)}s` - return `${ms}ms` -} - -function statusLabel(status: Exchange['status']): string { - switch (status) { - case 'completed': return 'OK' - case 'failed': return 'ERR' - case 'running': return 'RUN' - case 'warning': return 'WARN' - } -} - -function statusToVariant(status: Exchange['status']): string { - switch (status) { - case 'completed': return 'success' - case 'failed': return 'error' - case 'running': return 'running' - case 'warning': return 'warning' - } -} - -function formatTimestamp(date: Date): string { - return date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) -} +import { formatDuration, statusLabel, statusToVariant, formatTimestamp } from '../utils/format-utils' function healthToColor(health: SidebarApp['health']): string { switch (health) { diff --git a/src/pages/Dashboard/Dashboard.tsx b/src/pages/Dashboard/Dashboard.tsx index 4e45016..e5549e7 100644 --- a/src/pages/Dashboard/Dashboard.tsx +++ b/src/pages/Dashboard/Dashboard.tsx @@ -25,6 +25,9 @@ import { Badge } from '../../design-system/primitives/Badge/Badge' // Global filters import { useGlobalFilters } from '../../design-system/providers/GlobalFilterProvider' +// Utils +import { formatDuration, statusToVariant, statusLabel, toRouteNodeType, durationClass } from '../../utils/format-utils' + // Mock data import { exchanges, type Exchange } from '../../mocks/exchanges' import { kpiMetrics, type KpiMetric } from '../../mocks/metrics' @@ -66,12 +69,6 @@ const kpiItems: KpiItem[] = kpiMetrics.map((m) => ({ })) // ─── Helpers ───────────────────────────────────────────────────────────────── -function formatDuration(ms: number): string { - if (ms >= 60_000) return `${(ms / 1000).toFixed(0)}s` - if (ms >= 1000) return `${(ms / 1000).toFixed(2)}s` - return `${ms}ms` -} - function formatTimestamp(date: Date): string { const y = date.getFullYear() const mo = String(date.getMonth() + 1).padStart(2, '0') @@ -82,24 +79,6 @@ function formatTimestamp(date: Date): string { return `${y}-${mo}-${d} ${h}:${mi}:${s}` } -function statusToVariant(status: Exchange['status']): 'success' | 'error' | 'running' | 'warning' { - switch (status) { - case 'completed': return 'success' - case 'failed': return 'error' - case 'running': return 'running' - case 'warning': return 'warning' - } -} - -function statusLabel(status: Exchange['status']): string { - switch (status) { - case 'completed': return 'OK' - case 'failed': return 'ERR' - case 'running': return 'RUN' - case 'warning': return 'WARN' - } -} - // ─── Table columns (base, without navigate action) ────────────────────────── const BASE_COLUMNS: Column[] = [ { @@ -150,7 +129,7 @@ const BASE_COLUMNS: Column[] = [ header: 'Duration', sortable: true, render: (_, row) => ( - + {formatDuration(row.durationMs)} ), @@ -167,14 +146,6 @@ const BASE_COLUMNS: Column[] = [ }, ] -function durationClass(ms: number, status: Exchange['status']): string { - if (status === 'failed') return styles.durBreach - if (ms < 100) return styles.durFast - if (ms < 200) return styles.durNormal - if (ms < 300) return styles.durSlow - return styles.durBreach -} - const SHORTCUTS = [ { keys: 'Ctrl+K', label: 'Search' }, { keys: '↑↓', label: 'Navigate rows' }, @@ -257,16 +228,6 @@ export function Dashboard() { return undefined } - // Map processor types to RouteNode types - function toRouteNodeType(procType: string): RouteNode['type'] { - switch (procType) { - case 'consumer': return 'from' - case 'transform': return 'process' - case 'enrich': return 'process' - default: return procType as RouteNode['type'] - } - } - // Build RouteFlow nodes from exchange processors const routeNodes: RouteNode[] = selectedExchange ? selectedExchange.processors.map((p) => ({ diff --git a/src/pages/ExchangeDetail/ExchangeDetail.tsx b/src/pages/ExchangeDetail/ExchangeDetail.tsx index 48be210..5d4af60 100644 --- a/src/pages/ExchangeDetail/ExchangeDetail.tsx +++ b/src/pages/ExchangeDetail/ExchangeDetail.tsx @@ -18,6 +18,9 @@ import { MonoText } from '../../design-system/primitives/MonoText/MonoText' import { CodeBlock } from '../../design-system/primitives/CodeBlock/CodeBlock' import { InfoCallout } from '../../design-system/primitives/InfoCallout/InfoCallout' +// Utils +import { formatDuration, statusToVariant, toRouteNodeType } from '../../utils/format-utils' + // Mock data import { exchanges } from '../../mocks/exchanges' import { buildRouteToAppMap } from '../../mocks/sidebar' @@ -25,21 +28,6 @@ import { buildRouteToAppMap } from '../../mocks/sidebar' const ROUTE_TO_APP = buildRouteToAppMap() // ─── Helpers ────────────────────────────────────────────────────────────────── -function formatDuration(ms: number): string { - if (ms >= 60_000) return `${(ms / 1000).toFixed(0)}s` - if (ms >= 1000) return `${(ms / 1000).toFixed(2)}s` - return `${ms}ms` -} - -function statusToVariant(status: 'completed' | 'failed' | 'running' | 'warning'): 'success' | 'error' | 'running' | 'warning' { - switch (status) { - case 'completed': return 'success' - case 'failed': return 'error' - case 'running': return 'running' - case 'warning': return 'warning' - } -} - function statusToLabel(status: 'completed' | 'failed' | 'running' | 'warning'): string { switch (status) { case 'completed': return 'COMPLETED' @@ -145,16 +133,6 @@ function generateExchangeSnapshotOut( } } -// Map processor types to RouteNode types -function toRouteNodeType(procType: string): RouteNode['type'] { - switch (procType) { - case 'consumer': return 'from' - case 'transform': return 'process' - case 'enrich': return 'process' - default: return procType as RouteNode['type'] - } -} - // ─── ExchangeDetail component ───────────────────────────────────────────────── export function ExchangeDetail() { const { id } = useParams<{ id: string }>() diff --git a/src/pages/RouteDetail/RouteDetail.tsx b/src/pages/RouteDetail/RouteDetail.tsx index e29a37f..d0606bc 100644 --- a/src/pages/RouteDetail/RouteDetail.tsx +++ b/src/pages/RouteDetail/RouteDetail.tsx @@ -17,39 +17,14 @@ import { StatusDot } from '../../design-system/primitives/StatusDot/StatusDot' import { MonoText } from '../../design-system/primitives/MonoText/MonoText' import { InfoCallout } from '../../design-system/primitives/InfoCallout/InfoCallout' +// Utils +import { formatDuration, statusToVariant, statusLabel, formatTimestamp, durationClass } from '../../utils/format-utils' + // Mock data import { routes } from '../../mocks/routes' import { exchanges, type Exchange } from '../../mocks/exchanges' // ─── Helpers ────────────────────────────────────────────────────────────────── -function formatDuration(ms: number): string { - if (ms >= 60_000) return `${(ms / 1000).toFixed(0)}s` - if (ms >= 1000) return `${(ms / 1000).toFixed(2)}s` - return `${ms}ms` -} - -function formatTimestamp(date: Date): string { - return date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) -} - -function statusToVariant(status: Exchange['status']): 'success' | 'error' | 'running' | 'warning' { - switch (status) { - case 'completed': return 'success' - case 'failed': return 'error' - case 'running': return 'running' - case 'warning': return 'warning' - } -} - -function statusLabel(status: Exchange['status']): string { - switch (status) { - case 'completed': return 'OK' - case 'failed': return 'ERR' - case 'running': return 'RUN' - case 'warning': return 'WARN' - } -} - function routeStatusVariant(status: 'healthy' | 'degraded' | 'down'): 'success' | 'warning' | 'error' { switch (status) { case 'healthy': return 'success' @@ -58,14 +33,6 @@ function routeStatusVariant(status: 'healthy' | 'degraded' | 'down'): 'success' } } -function durationClass(ms: number, status: Exchange['status']): string { - if (status === 'failed') return styles.durBreach - if (ms < 100) return styles.durFast - if (ms < 200) return styles.durNormal - if (ms < 300) return styles.durSlow - return styles.durBreach -} - // ─── Columns for exchanges table ──────────────────────────────────────────── const EXCHANGE_COLUMNS: Column[] = [ { @@ -106,7 +73,7 @@ const EXCHANGE_COLUMNS: Column[] = [ header: 'Duration', sortable: true, render: (_, row) => ( - + {formatDuration(row.durationMs)} ), diff --git a/src/utils/format-utils.ts b/src/utils/format-utils.ts new file mode 100644 index 0000000..7d7221c --- /dev/null +++ b/src/utils/format-utils.ts @@ -0,0 +1,55 @@ +import type { RouteNode } from '../design-system/composites/RouteFlow/RouteFlow' + +export function formatDuration(ms: number): string { + if (ms >= 60_000) return `${(ms / 1000).toFixed(0)}s` + if (ms >= 1000) return `${(ms / 1000).toFixed(2)}s` + return `${ms}ms` +} + +export function statusToVariant(status: string): 'success' | 'error' | 'running' | 'warning' { + switch (status) { + case 'completed': return 'success' + case 'failed': return 'error' + case 'running': return 'running' + case 'warning': return 'warning' + default: return 'warning' + } +} + +export function statusLabel(s: string): string { + switch (s) { + case 'completed': return 'OK' + case 'failed': return 'ERR' + case 'running': return 'RUN' + case 'warning': return 'WARN' + default: return s + } +} + +export function formatTimestamp(date: Date): string { + return date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) +} + +export function toRouteNodeType(procType: string): RouteNode['type'] { + switch (procType) { + case 'consumer': return 'from' + case 'transform': return 'process' + case 'enrich': return 'process' + default: return procType as RouteNode['type'] + } +} + +interface DurationStyles { + durBreach: string + durFast: string + durNormal: string + durSlow: string +} + +export function durationClass(ms: number, status: string, styles: DurationStyles): string { + if (status === 'failed') return styles.durBreach + if (ms < 100) return styles.durFast + if (ms < 200) return styles.durNormal + if (ms < 300) return styles.durSlow + return styles.durBreach +}