diff --git a/ui/web/src/pages/channels/channel-detail/channel-detail-page.tsx b/ui/web/src/pages/channels/channel-detail/channel-detail-page.tsx index 895e8149..dce2aa1c 100644 --- a/ui/web/src/pages/channels/channel-detail/channel-detail-page.tsx +++ b/ui/web/src/pages/channels/channel-detail/channel-detail-page.tsx @@ -21,6 +21,7 @@ import { getChannelRemediationMeta, getRenderableChannelStatus, getChannelStatusMeta, + shouldShowChannelDiagnosticsCard, } from "../channels-status-view"; import { useChannelTimeline } from "./channel-detail-timeline-hook"; import { ChannelDetailDialogs } from "./channel-detail-dialogs"; @@ -139,12 +140,7 @@ export function ChannelDetailPage({ const timelineItems = useChannelTimeline(status, t); - const showDiagnosticsCard = - status?.state === "failed" || - status?.state === "degraded" || - !!status?.remediation || - !!status?.consecutive_failures || - !!status?.first_failed_at; + const showDiagnosticsCard = shouldShowChannelDiagnosticsCard(status); const neutralHealthNote = !showDiagnosticsCard && diff --git a/ui/web/src/pages/channels/channels-status-utils.test.ts b/ui/web/src/pages/channels/channels-status-utils.test.ts new file mode 100644 index 00000000..b6c0290f --- /dev/null +++ b/ui/web/src/pages/channels/channels-status-utils.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from "vitest"; +import type { ChannelRuntimeStatus } from "@/types/channel"; +import { shouldShowChannelDiagnosticsCard } from "./channels-status-utils"; + +function status(overrides: Partial): ChannelRuntimeStatus { + return { + enabled: true, + running: true, + state: "healthy", + ...overrides, + }; +} + +describe("shouldShowChannelDiagnosticsCard", () => { + it("ignores Go zero-value first_failed_at timestamps on healthy channels", () => { + expect( + shouldShowChannelDiagnosticsCard( + status({ first_failed_at: "0001-01-01T00:00:00Z" }), + ), + ).toBe(false); + }); + + it("shows diagnostics for meaningful failure timestamps", () => { + expect( + shouldShowChannelDiagnosticsCard( + status({ first_failed_at: "2026-01-01T00:00:00Z" }), + ), + ).toBe(true); + }); + + it("still shows diagnostics for active degraded or failed states", () => { + expect(shouldShowChannelDiagnosticsCard(status({ state: "degraded" }))).toBe(true); + expect(shouldShowChannelDiagnosticsCard(status({ state: "failed" }))).toBe(true); + }); +}); diff --git a/ui/web/src/pages/channels/channels-status-utils.ts b/ui/web/src/pages/channels/channels-status-utils.ts index 12e9f958..70af4949 100644 --- a/ui/web/src/pages/channels/channels-status-utils.ts +++ b/ui/web/src/pages/channels/channels-status-utils.ts @@ -161,6 +161,18 @@ export function getChannelCheckedLabel( }); } +export function shouldShowChannelDiagnosticsCard( + status: ChannelRuntimeStatus | null | undefined, +) { + return ( + status?.state === "failed" || + status?.state === "degraded" || + !!status?.remediation || + !!status?.consecutive_failures || + !!formatRelativeTime(status?.first_failed_at) + ); +} + export function getChannelFailureKindLabel( kind: ChannelRuntimeStatus["failure_kind"], t: TFunction, diff --git a/ui/web/src/pages/channels/channels-status-view.tsx b/ui/web/src/pages/channels/channels-status-view.tsx index 5b71220e..9a86de1f 100644 --- a/ui/web/src/pages/channels/channels-status-view.tsx +++ b/ui/web/src/pages/channels/channels-status-view.tsx @@ -23,6 +23,7 @@ export { getRenderableChannelStatus, getChannelCheckedLabel, getChannelFailureKindLabel, + shouldShowChannelDiagnosticsCard, getChannelAttentionPriority, getChannelStatusMeta, getChannelRemediationMeta,