import * as React from 'react' import { cn } from '../../lib/utils' import { Rss } from 'lucide-react' import { formatRelativeTime } from '../../utils' import { HealthCard, HealthDot } from './HealthCard' import { worstStatus, type HealthStatus } from './health-status' export interface SourceFreshnessItem { name: string /** Last time this source delivered data. Null/undefined = never = stale. */ lastUpdated?: string | Date | null /** Explicit status; otherwise derived from lastUpdated vs staleAfterHours. */ status?: HealthStatus } export interface SourceFreshnessProps { title?: string sources: SourceFreshnessItem[] /** A source with no update within this window is stale (default 48h). */ staleAfterHours?: number /** Compare against this instant (injectable for tests); defaults to Date.now(). */ now?: number emptyMessage?: string className?: string } function deriveStatus(item: SourceFreshnessItem, staleAfterHours: number, now: number): HealthStatus { if (item.status) return item.status if (!item.lastUpdated) return 'critical' const ts = new Date(item.lastUpdated).getTime() if (Number.isNaN(ts)) return 'neutral' const ageHours = (now - ts) / 3_600_000 if (ageHours <= staleAfterHours) return 'ok' if (ageHours <= staleAfterHours * 2) return 'warn' return 'critical' } /** * Source / ingestion freshness: which upstream feeds are fresh vs stale. A stale * source is a bug (everything is ongoing — no one-time fetches), so any stale * feed pulls the whole card to warn/critical. Presentational — a tenant widget * feeds it the source records + their last-fetched markers. */ export function SourceFreshness({ title = 'Source freshness', sources, staleAfterHours = 48, now, emptyMessage = 'No sources configured.', className, }: SourceFreshnessProps) { const at = now ?? Date.now() const rows = sources.map((s) => ({ ...s, resolved: deriveStatus(s, staleAfterHours, at) })) const staleCount = rows.filter((r) => r.resolved === 'warn' || r.resolved === 'critical').length const status = worstStatus(rows.map((r) => r.resolved)) const statusLabel = sources.length === 0 ? undefined : staleCount > 0 ? `${staleCount} stale` : 'All fresh' return ( ) }