Files
go-otel/otel-ui/src/components/AnomalyRadarBar.tsx
T
mats 97743ea21c
Build and Publish / Metadata (push) Successful in 2s
Build and Publish / BuildAndDeployAmd64 (push) Successful in 1m4s
sourcemap view
2026-09-19 15:49:44 +02:00

94 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { AlertTriangle, ArrowRight } from "lucide-react"
import React from "react"
import { useObservabilityData } from "../hooks/useObservabilityData"
import { useIssues } from "../hooks/useIssues"
import { getServiceName, isSpanError } from "../lib/otel-utils"
/** Full-width alert strip docked under the top bar; renders nothing when there is nothing anomalous. */
export const AnomalyRadarBar: React.FC = () => {
const { logs, traces, showErrorsOnly, setShowErrorsOnly, setActiveTab } = useObservabilityData()
const { issues } = useIssues()
const errorStats = React.useMemo(() => {
let errorCount = 0
const services = new Set<string>()
const note = (name: string) => { if (name !== "unknown-service") services.add(name) }
logs.forEach((l) =>
(l.scopeLogs ?? []).forEach((sl) =>
(sl.logRecords ?? []).forEach((r) => {
if ((r.severityNumber ?? 0) >= 17) {
errorCount++
note(getServiceName(l.resource?.attributes ?? [], r.attributes ?? []))
}
}),
),
)
traces.forEach((t) =>
(t.resourceSpans ?? []).forEach((rs) =>
(rs.scopeSpans ?? []).forEach((ss) =>
(ss.spans ?? []).forEach((s) => {
if (isSpanError(s)) {
errorCount++
note(getServiceName(rs.resource?.attributes ?? [], s.attributes ?? []))
}
}),
),
),
)
return { errorCount, services: Array.from(services) }
}, [logs, traces])
const topOpenIssue = React.useMemo(
() => issues.filter((i) => !i.status || i.status === "open").sort((a, b) => b.count - a.count)[0] ?? null,
[issues],
)
if (errorStats.errorCount === 0 && !topOpenIssue) return null
const services = errorStats.services.slice(0, 3).join(", ") + (errorStats.services.length > 3 ? ` +${errorStats.services.length - 3}` : "")
return (
<div
role="alert"
className="sticky top-[58px] z-30 flex flex-wrap items-center gap-x-4 gap-y-1.5 border-b-2 border-ds-critical bg-ds-critical-bg px-6 py-2 font-[family-name:var(--font-ui)]"
>
<span className="inline-flex shrink-0 items-center gap-1.5 text-[11px] font-bold uppercase tracking-[0.1em] text-ds-critical">
<AlertTriangle className="h-3.5 w-3.5" aria-hidden="true" />
Anomaly radar
</span>
<span className="min-w-0 flex-1 truncate text-[12.5px] text-ds-text">
{errorStats.errorCount > 0 && (
<><strong className="font-semibold">{errorStats.errorCount} errors</strong> in the active window{services && <span className="text-ds-text-2"> · {services}</span>}</>
)}
{topOpenIssue && (
<span className="text-ds-text-2">
{errorStats.errorCount > 0 ? " · top issue " : ""}
<span className="font-medium text-ds-text">{topOpenIssue.title}</span> ×{topOpenIssue.count} ({topOpenIssue.service})
</span>
)}
</span>
<div className="flex shrink-0 items-center gap-2">
{!showErrorsOnly && (
<button
type="button"
onClick={() => setShowErrorsOnly(true)}
className="inline-flex h-7 items-center rounded-[8px] border border-ds-critical/40 px-2.5 text-[12px] font-medium text-ds-critical hover:bg-ds-critical/10"
>
Isolate errors
</button>
)}
<button
type="button"
onClick={() => setActiveTab("tab0")}
className="inline-flex h-7 items-center gap-1 rounded-[8px] bg-ds-critical px-2.5 text-[12px] font-semibold text-white hover:opacity-90"
>
Triage issues <ArrowRight className="h-3 w-3" aria-hidden="true" />
</button>
</div>
</div>
)
}
export default AnomalyRadarBar