refactor: extract duplicated utility functions into shared format-utils
All checks were successful
Build & Publish / publish (push) Successful in 1m12s
All checks were successful
Build & Publish / publish (push) Successful in 1m12s
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) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import type { ReactNode } from 'react'
|
|||||||
import { Play, Cog, Square, Diamond, AlertTriangle, EllipsisVertical } from 'lucide-react'
|
import { Play, Cog, Square, Diamond, AlertTriangle, EllipsisVertical } from 'lucide-react'
|
||||||
import styles from './RouteFlow.module.css'
|
import styles from './RouteFlow.module.css'
|
||||||
import { Dropdown } from '../Dropdown/Dropdown'
|
import { Dropdown } from '../Dropdown/Dropdown'
|
||||||
|
import { formatDuration } from '../../../utils/format-utils'
|
||||||
|
|
||||||
export interface NodeBadge {
|
export interface NodeBadge {
|
||||||
label: string
|
label: string
|
||||||
@@ -42,12 +43,6 @@ interface RouteFlowProps {
|
|||||||
className?: string
|
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 {
|
function durationClass(ms: number, status: string): string {
|
||||||
if (status === 'fail') return styles.durBreach
|
if (status === 'fail') return styles.durBreach
|
||||||
if (ms < 50) return styles.durFast
|
if (ms < 50) return styles.durFast
|
||||||
|
|||||||
@@ -3,34 +3,7 @@ import { exchanges, type Exchange } from './exchanges'
|
|||||||
import { routes } from './routes'
|
import { routes } from './routes'
|
||||||
import { agents } from './agents'
|
import { agents } from './agents'
|
||||||
import { SIDEBAR_APPS, buildRouteToAppMap, type SidebarApp } from './sidebar'
|
import { SIDEBAR_APPS, buildRouteToAppMap, type SidebarApp } from './sidebar'
|
||||||
|
import { formatDuration, statusLabel, statusToVariant, formatTimestamp } from '../utils/format-utils'
|
||||||
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' })
|
|
||||||
}
|
|
||||||
|
|
||||||
function healthToColor(health: SidebarApp['health']): string {
|
function healthToColor(health: SidebarApp['health']): string {
|
||||||
switch (health) {
|
switch (health) {
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ import { Badge } from '../../design-system/primitives/Badge/Badge'
|
|||||||
// Global filters
|
// Global filters
|
||||||
import { useGlobalFilters } from '../../design-system/providers/GlobalFilterProvider'
|
import { useGlobalFilters } from '../../design-system/providers/GlobalFilterProvider'
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
import { formatDuration, statusToVariant, statusLabel, toRouteNodeType, durationClass } from '../../utils/format-utils'
|
||||||
|
|
||||||
// Mock data
|
// Mock data
|
||||||
import { exchanges, type Exchange } from '../../mocks/exchanges'
|
import { exchanges, type Exchange } from '../../mocks/exchanges'
|
||||||
import { kpiMetrics, type KpiMetric } from '../../mocks/metrics'
|
import { kpiMetrics, type KpiMetric } from '../../mocks/metrics'
|
||||||
@@ -66,12 +69,6 @@ const kpiItems: KpiItem[] = kpiMetrics.map((m) => ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
// ─── 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 {
|
function formatTimestamp(date: Date): string {
|
||||||
const y = date.getFullYear()
|
const y = date.getFullYear()
|
||||||
const mo = String(date.getMonth() + 1).padStart(2, '0')
|
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}`
|
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) ──────────────────────────
|
// ─── Table columns (base, without navigate action) ──────────────────────────
|
||||||
const BASE_COLUMNS: Column<Exchange>[] = [
|
const BASE_COLUMNS: Column<Exchange>[] = [
|
||||||
{
|
{
|
||||||
@@ -150,7 +129,7 @@ const BASE_COLUMNS: Column<Exchange>[] = [
|
|||||||
header: 'Duration',
|
header: 'Duration',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (_, row) => (
|
render: (_, row) => (
|
||||||
<MonoText size="sm" className={durationClass(row.durationMs, row.status)}>
|
<MonoText size="sm" className={durationClass(row.durationMs, row.status, styles)}>
|
||||||
{formatDuration(row.durationMs)}
|
{formatDuration(row.durationMs)}
|
||||||
</MonoText>
|
</MonoText>
|
||||||
),
|
),
|
||||||
@@ -167,14 +146,6 @@ const BASE_COLUMNS: Column<Exchange>[] = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
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 = [
|
const SHORTCUTS = [
|
||||||
{ keys: 'Ctrl+K', label: 'Search' },
|
{ keys: 'Ctrl+K', label: 'Search' },
|
||||||
{ keys: '↑↓', label: 'Navigate rows' },
|
{ keys: '↑↓', label: 'Navigate rows' },
|
||||||
@@ -257,16 +228,6 @@ export function Dashboard() {
|
|||||||
return undefined
|
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
|
// Build RouteFlow nodes from exchange processors
|
||||||
const routeNodes: RouteNode[] = selectedExchange
|
const routeNodes: RouteNode[] = selectedExchange
|
||||||
? selectedExchange.processors.map((p) => ({
|
? selectedExchange.processors.map((p) => ({
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ import { MonoText } from '../../design-system/primitives/MonoText/MonoText'
|
|||||||
import { CodeBlock } from '../../design-system/primitives/CodeBlock/CodeBlock'
|
import { CodeBlock } from '../../design-system/primitives/CodeBlock/CodeBlock'
|
||||||
import { InfoCallout } from '../../design-system/primitives/InfoCallout/InfoCallout'
|
import { InfoCallout } from '../../design-system/primitives/InfoCallout/InfoCallout'
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
import { formatDuration, statusToVariant, toRouteNodeType } from '../../utils/format-utils'
|
||||||
|
|
||||||
// Mock data
|
// Mock data
|
||||||
import { exchanges } from '../../mocks/exchanges'
|
import { exchanges } from '../../mocks/exchanges'
|
||||||
import { buildRouteToAppMap } from '../../mocks/sidebar'
|
import { buildRouteToAppMap } from '../../mocks/sidebar'
|
||||||
@@ -25,21 +28,6 @@ import { buildRouteToAppMap } from '../../mocks/sidebar'
|
|||||||
const ROUTE_TO_APP = buildRouteToAppMap()
|
const ROUTE_TO_APP = buildRouteToAppMap()
|
||||||
|
|
||||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
// ─── 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 {
|
function statusToLabel(status: 'completed' | 'failed' | 'running' | 'warning'): string {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'completed': return 'COMPLETED'
|
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 ─────────────────────────────────────────────────
|
// ─── ExchangeDetail component ─────────────────────────────────────────────────
|
||||||
export function ExchangeDetail() {
|
export function ExchangeDetail() {
|
||||||
const { id } = useParams<{ id: string }>()
|
const { id } = useParams<{ id: string }>()
|
||||||
|
|||||||
@@ -17,39 +17,14 @@ import { StatusDot } from '../../design-system/primitives/StatusDot/StatusDot'
|
|||||||
import { MonoText } from '../../design-system/primitives/MonoText/MonoText'
|
import { MonoText } from '../../design-system/primitives/MonoText/MonoText'
|
||||||
import { InfoCallout } from '../../design-system/primitives/InfoCallout/InfoCallout'
|
import { InfoCallout } from '../../design-system/primitives/InfoCallout/InfoCallout'
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
import { formatDuration, statusToVariant, statusLabel, formatTimestamp, durationClass } from '../../utils/format-utils'
|
||||||
|
|
||||||
// Mock data
|
// Mock data
|
||||||
import { routes } from '../../mocks/routes'
|
import { routes } from '../../mocks/routes'
|
||||||
import { exchanges, type Exchange } from '../../mocks/exchanges'
|
import { exchanges, type Exchange } from '../../mocks/exchanges'
|
||||||
|
|
||||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
// ─── 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' {
|
function routeStatusVariant(status: 'healthy' | 'degraded' | 'down'): 'success' | 'warning' | 'error' {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'healthy': return 'success'
|
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 ────────────────────────────────────────────
|
// ─── Columns for exchanges table ────────────────────────────────────────────
|
||||||
const EXCHANGE_COLUMNS: Column<Exchange>[] = [
|
const EXCHANGE_COLUMNS: Column<Exchange>[] = [
|
||||||
{
|
{
|
||||||
@@ -106,7 +73,7 @@ const EXCHANGE_COLUMNS: Column<Exchange>[] = [
|
|||||||
header: 'Duration',
|
header: 'Duration',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (_, row) => (
|
render: (_, row) => (
|
||||||
<MonoText size="sm" className={durationClass(row.durationMs, row.status)}>
|
<MonoText size="sm" className={durationClass(row.durationMs, row.status, styles)}>
|
||||||
{formatDuration(row.durationMs)}
|
{formatDuration(row.durationMs)}
|
||||||
</MonoText>
|
</MonoText>
|
||||||
),
|
),
|
||||||
|
|||||||
55
src/utils/format-utils.ts
Normal file
55
src/utils/format-utils.ts
Normal file
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user