Unify otel-ui on ds-* theme; restore redesigned Issues view
Restore IssuesVisualizer redesign reverted by merge 67ce419 and re-apply
Needle triage/test additions. Migrate all remaining screens off the MD3
palette, raw Tailwind colors, gradients and Material Symbols. Remove the
legacy theme block and fonts, drop the duplicate tab strip, rebuild
TelemetryFilters on FilterBar, and document status in AGENTS.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
67ce4197da
commit
1e9482ed21
23 files changed
+1583
-1648
No files matched your search
+35
-164
@@ -2,20 +2,11 @@ import { useCallback, useState } from "react"
|
||||
import * as Tabs from "@radix-ui/react-tabs"
|
||||
import {
|
||||
Activity,
|
||||
AlertOctagon,
|
||||
LineChart,
|
||||
ScrollText,
|
||||
AlertTriangle,
|
||||
Info,
|
||||
CheckCircle2,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
TrendingUp,
|
||||
Wrench,
|
||||
Radar,
|
||||
Globe,
|
||||
GitBranch,
|
||||
CloudCog,
|
||||
} from "lucide-react"
|
||||
import type { LucideIcon } from "lucide-react"
|
||||
import { MetricsVisualizer } from "./MetricsVisualizer"
|
||||
@@ -39,7 +30,8 @@ import { RolloutsVisualizer } from "./RolloutsVisualizer"
|
||||
|
||||
type SeverityMeta = {
|
||||
baseLabel: string
|
||||
gradient: string
|
||||
bar: string
|
||||
badge: string
|
||||
ring: string
|
||||
icon: LucideIcon
|
||||
}
|
||||
@@ -47,34 +39,39 @@ type SeverityMeta = {
|
||||
const severityStyles: Record<string, SeverityMeta> = {
|
||||
ERROR: {
|
||||
baseLabel: "Error",
|
||||
gradient: "from-rose-500 via-red-500 to-orange-400",
|
||||
ring: "ring-rose-400/30",
|
||||
bar: "bg-ds-critical",
|
||||
badge: "bg-ds-critical-bg text-ds-critical",
|
||||
ring: "ring-ds-critical/40",
|
||||
icon: AlertTriangle,
|
||||
},
|
||||
WARN: {
|
||||
baseLabel: "Warning",
|
||||
gradient: "from-amber-400 via-orange-400 to-rose-400",
|
||||
ring: "ring-amber-300/30",
|
||||
bar: "bg-ds-warn",
|
||||
badge: "bg-ds-warn-bg text-ds-warn",
|
||||
ring: "ring-ds-warn/40",
|
||||
icon: AlertTriangle,
|
||||
},
|
||||
INFO: {
|
||||
baseLabel: "Info",
|
||||
gradient: "from-sky-500 via-blue-500 to-violet-500",
|
||||
ring: "ring-sky-400/30",
|
||||
bar: "bg-ds-accent",
|
||||
badge: "bg-ds-accent-bg text-ds-accent",
|
||||
ring: "ring-ds-accent/40",
|
||||
icon: Info,
|
||||
},
|
||||
DEBUG: {
|
||||
baseLabel: "Debug",
|
||||
gradient: "from-emerald-400 via-teal-400 to-cyan-400",
|
||||
ring: "ring-emerald-300/30",
|
||||
bar: "bg-ds-ok",
|
||||
badge: "bg-ds-ok/15 text-ds-ok",
|
||||
ring: "ring-ds-ok/40",
|
||||
icon: CheckCircle2,
|
||||
},
|
||||
}
|
||||
|
||||
const fallbackSeverity: SeverityMeta = {
|
||||
baseLabel: "Log",
|
||||
gradient: "from-slate-600 via-slate-500 to-slate-400",
|
||||
ring: "ring-white/20",
|
||||
bar: "bg-ds-line-strong",
|
||||
badge: "bg-ds-surface-2 text-ds-text-2",
|
||||
ring: "ring-ds-line-strong",
|
||||
icon: Info,
|
||||
}
|
||||
|
||||
@@ -159,17 +156,17 @@ export const LogEntryView = ({ log }: { log: LogEntry }) => {
|
||||
return (
|
||||
<div
|
||||
key={recordKey}
|
||||
className="group relative flex flex-col overflow-hidden rounded-xl border border-white/5 bg-slate-950/40 transition-all hover:border-white/10 hover:bg-slate-900/50"
|
||||
className="group relative flex flex-col overflow-hidden rounded-xl border border-ds-line bg-ds-surface-2 transition-all hover:border-ds-line hover:bg-ds-surface-2"
|
||||
>
|
||||
{/* The Main Log Row */}
|
||||
<div
|
||||
onClick={() => setExpandedId(isExpanded ? null : recordKey)}
|
||||
className="flex cursor-pointer flex-wrap items-center gap-x-4 gap-y-2 p-2.5 font-mono text-[12px]"
|
||||
>
|
||||
<div className={`absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b ${severity.gradient} opacity-40 transition-opacity group-hover:opacity-100`} />
|
||||
<div className={`absolute left-0 top-0 bottom-0 w-1 ${severity.bar} opacity-40 transition-opacity group-hover:opacity-100`} />
|
||||
|
||||
{/* Timestamp */}
|
||||
<span className="shrink-0 text-slate-500 whitespace-nowrap ml-1">
|
||||
<span className="shrink-0 text-ds-text-3 whitespace-nowrap ml-1">
|
||||
{formatTimestamp(record.timeUnixNano)}
|
||||
</span>
|
||||
|
||||
@@ -185,12 +182,12 @@ export const LogEntryView = ({ log }: { log: LogEntry }) => {
|
||||
})()}
|
||||
|
||||
{/* Severity Badge */}
|
||||
<span className={`shrink-0 rounded px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider bg-gradient-to-r ${severity.gradient} text-slate-950`}>
|
||||
<span className={`shrink-0 rounded px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider ${severity.badge}`}>
|
||||
{severity.display}
|
||||
</span>
|
||||
|
||||
{/* Log Message */}
|
||||
<span className="flex-1 min-w-[300px] break-all text-slate-200">
|
||||
<span className="flex-1 min-w-[300px] break-all text-ds-text">
|
||||
{bodyMessage}
|
||||
</span>
|
||||
|
||||
@@ -202,28 +199,28 @@ export const LogEntryView = ({ log }: { log: LogEntry }) => {
|
||||
e.stopPropagation()
|
||||
navigateToSpan(record.traceId!, record.spanId)
|
||||
}}
|
||||
className="group/trace flex items-center gap-1.5 rounded-full bg-sky-500/10 border border-sky-400/20 px-2.5 py-0.5 text-[10px] font-bold text-sky-400 hover:bg-sky-500/20 hover:border-sky-400/30 transition-all"
|
||||
className="group/trace flex items-center gap-1.5 rounded-full bg-ds-accent-bg border border-ds-accent/20 px-2.5 py-0.5 text-[10px] font-bold text-ds-accent hover:bg-ds-accent-bg hover:border-ds-accent/30 transition-all"
|
||||
>
|
||||
<Activity className="h-2.5 w-2.5 group-hover/trace:animate-pulse" />
|
||||
TRACE:{record.traceId.slice(0, 6)}
|
||||
</button>
|
||||
)}
|
||||
{isExpanded ? <ChevronUp className="h-3 w-3 text-slate-500" /> : <ChevronDown className="h-3 w-3 text-slate-500" />}
|
||||
{isExpanded ? <ChevronUp className="h-3 w-3 text-ds-text-3" /> : <ChevronDown className="h-3 w-3 text-ds-text-3" />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Expansion Content */}
|
||||
{isExpanded && (
|
||||
<div className="border-t border-white/5 bg-black/40 p-4 text-[12px]">
|
||||
<div className="border-t border-ds-line bg-ds-surface-2 p-4 text-[12px]">
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
{(record.attributes ?? []).length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-[10px] font-bold uppercase tracking-widest text-slate-500">Attributes</p>
|
||||
<p className="text-[10px] font-bold uppercase tracking-widest text-ds-text-3">Attributes</p>
|
||||
<div className="space-y-1">
|
||||
{(record.attributes ?? []).map(attr => (
|
||||
<div key={attr.key} className="flex gap-2">
|
||||
<span className="text-sky-400 shrink-0">{attr.key}:</span>
|
||||
<span className="text-slate-300 break-all">{formatAttributeValue(attr.value)}</span>
|
||||
<span className="text-ds-accent shrink-0">{attr.key}:</span>
|
||||
<span className="text-ds-text-2 break-all">{formatAttributeValue(attr.value)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -231,20 +228,20 @@ export const LogEntryView = ({ log }: { log: LogEntry }) => {
|
||||
)}
|
||||
{(record.resourceAttributes ?? []).length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-[10px] font-bold uppercase tracking-widest text-slate-500">Resource</p>
|
||||
<p className="text-[10px] font-bold uppercase tracking-widest text-ds-text-3">Resource</p>
|
||||
<div className="space-y-1">
|
||||
{(record.resourceAttributes ?? []).map(attr => (
|
||||
<div key={attr.key} className="flex gap-2">
|
||||
<span className="text-emerald-400 shrink-0">{attr.key}:</span>
|
||||
<span className="text-slate-400 break-all">{formatAttributeValue(attr.value)}</span>
|
||||
<span className="text-ds-ok shrink-0">{attr.key}:</span>
|
||||
<span className="text-ds-text-2 break-all">{formatAttributeValue(attr.value)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-4 pt-4 border-t border-white/5 flex flex-wrap items-center justify-between gap-4">
|
||||
<div className="flex gap-4 text-[10px] text-slate-500">
|
||||
<div className="mt-4 pt-4 border-t border-ds-line flex flex-wrap items-center justify-between gap-4">
|
||||
<div className="flex gap-4 text-[10px] text-ds-text-3">
|
||||
<span>Scope: {record.scope || "unknown"}</span>
|
||||
{record.spanId && <span>Span ID: {record.spanId}</span>}
|
||||
{record.traceId && <span>Trace ID: {record.traceId}</span>}
|
||||
@@ -253,7 +250,7 @@ export const LogEntryView = ({ log }: { log: LogEntry }) => {
|
||||
{record.traceId && (
|
||||
<button
|
||||
onClick={() => navigateToSpan(record.traceId!, record.spanId)}
|
||||
className="inline-flex items-center gap-2 rounded-xl bg-sky-500 px-4 py-2 text-[10px] font-black uppercase tracking-widest text-slate-950 transition-all hover:bg-sky-400 hover:scale-[1.02] active:scale-[0.98] shadow-lg shadow-sky-500/20"
|
||||
className="inline-flex items-center gap-2 rounded-xl bg-ds-accent px-4 py-2 text-[10px] font-black uppercase tracking-widest text-ds-on-accent transition-all hover:bg-ds-accent hover:scale-[1.02] active:scale-[0.98] shadow-lg "
|
||||
>
|
||||
<Activity className="h-3 w-3" />
|
||||
Jump to Span in Trace
|
||||
@@ -276,7 +273,7 @@ const LogVisualizer = ({ logs }: { logs: LogEntry[] }) => {
|
||||
}
|
||||
|
||||
export const TabsComponent = () => {
|
||||
const { traces, logs, metrics, activeTab, setActiveTab } = useObservabilityData()
|
||||
const { traces, logs, activeTab, setActiveTab } = useObservabilityData()
|
||||
const issuesState = useIssues()
|
||||
const [statusBusy, setStatusBusy] = useState(false)
|
||||
// Trends tab only exists when the backend has ClickHouse-backed stats;
|
||||
@@ -305,109 +302,6 @@ export const TabsComponent = () => {
|
||||
[issuesState],
|
||||
)
|
||||
|
||||
const numberFormatter = new Intl.NumberFormat("en-US", {
|
||||
notation: "compact",
|
||||
maximumFractionDigits: 1,
|
||||
})
|
||||
|
||||
const frontendCount = logs.reduce((acc, log) => {
|
||||
return (
|
||||
acc +
|
||||
(log.scopeLogs ?? []).reduce((sAcc, sl) => {
|
||||
return (
|
||||
sAcc +
|
||||
(sl.logRecords ?? []).filter((r) => {
|
||||
const attrs = r.attributes ?? []
|
||||
return (
|
||||
attrs.some((a) => a.key === "source" && a.value?.stringValue === "frontend") ||
|
||||
sl.scope?.name === "otel-rum" ||
|
||||
attrs.some((a) => a.key === "rum.vital") ||
|
||||
attrs.some((a) => a.key === "browser.url")
|
||||
)
|
||||
}).length
|
||||
)
|
||||
}, 0)
|
||||
)
|
||||
}, 0)
|
||||
|
||||
const tabItems = [
|
||||
{
|
||||
value: "tab0",
|
||||
label: "Issues",
|
||||
description: "Deduplicated errors",
|
||||
Icon: AlertOctagon,
|
||||
count: issuesState.issues.length
|
||||
},
|
||||
{
|
||||
value: "tab8",
|
||||
label: "Patterns",
|
||||
description: "Deterministic similarity review",
|
||||
Icon: GitBranch,
|
||||
count: undefined as number | undefined,
|
||||
},
|
||||
{
|
||||
value: "tab9",
|
||||
label: "Rollouts",
|
||||
description: "Cluster deploy correlation",
|
||||
Icon: CloudCog,
|
||||
count: undefined as number | undefined,
|
||||
},
|
||||
{
|
||||
value: "tab1",
|
||||
label: "Traces",
|
||||
description: "Span timelines & focus",
|
||||
Icon: Activity,
|
||||
count: traces.length
|
||||
},
|
||||
{
|
||||
value: "tab2",
|
||||
label: "Metrics",
|
||||
description: "Performance trends",
|
||||
Icon: LineChart,
|
||||
count: metrics.length
|
||||
},
|
||||
{
|
||||
value: "tab3",
|
||||
label: "Logs",
|
||||
description: "System log streams",
|
||||
Icon: ScrollText,
|
||||
count: logs.length
|
||||
},
|
||||
{
|
||||
value: "tab7",
|
||||
label: "RUM sessions",
|
||||
description: "Real-user sessions, worst experience first",
|
||||
Icon: Globe,
|
||||
count: frontendCount,
|
||||
},
|
||||
// Historical trends need the ClickHouse-backed stats endpoints.
|
||||
...(statsAvailable
|
||||
? [
|
||||
{
|
||||
value: "tab5",
|
||||
label: "Trends",
|
||||
description: "History graphs & release comparison",
|
||||
Icon: TrendingUp,
|
||||
count: undefined as number | undefined,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
value: "tab6",
|
||||
label: "Archive & Trap",
|
||||
description: "Persistent storage search & signal trap",
|
||||
Icon: Radar,
|
||||
count: undefined,
|
||||
},
|
||||
{
|
||||
value: "tab4",
|
||||
label: "Setup",
|
||||
description: "Instrument a new service",
|
||||
Icon: Wrench,
|
||||
count: undefined
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<TelemetryFilters />
|
||||
@@ -416,29 +310,6 @@ export const TabsComponent = () => {
|
||||
onValueChange={setActiveTab}
|
||||
className="flex flex-col gap-4"
|
||||
>
|
||||
<Tabs.List
|
||||
className="flex gap-8 border-b border-white/10 pb-2 mb-2 w-full overflow-x-auto"
|
||||
aria-label="Telemetry explorer views"
|
||||
>
|
||||
{tabItems.map(({ value, label, Icon, count }) => (
|
||||
<Tabs.Trigger
|
||||
key={value}
|
||||
value={value}
|
||||
id={`tab-trigger-${value}`}
|
||||
aria-label={`${label} tab`}
|
||||
className="group relative flex items-center gap-2.5 pb-2 text-sm font-semibold text-slate-400 outline-none transition-colors hover:text-slate-200 data-[state=active]:text-white"
|
||||
>
|
||||
<div className="absolute -bottom-[2px] left-0 right-0 h-0.5 bg-sky-400 opacity-0 transition-opacity group-data-[state=active]:opacity-100" />
|
||||
<Icon className="h-4 w-4 text-slate-500 group-data-[state=active]:text-sky-400" />
|
||||
<span aria-hidden="true">{label}</span>
|
||||
{count !== undefined && (
|
||||
<span className="inline-flex rounded-full bg-slate-800 px-2 py-0.5 text-[10px] font-bold text-slate-300 group-data-[state=active]:bg-sky-500/20 group-data-[state=active]:text-sky-300">
|
||||
{numberFormatter.format(count)}
|
||||
</span>
|
||||
)}
|
||||
</Tabs.Trigger>
|
||||
))}
|
||||
</Tabs.List>
|
||||
<Tabs.Content
|
||||
className="outline-none py-2"
|
||||
value="tab0"
|
||||
|
||||
Reference in new issue
Block a user