Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
320 changes: 232 additions & 88 deletions ecosystem-explorer/src/features/collector/collector-detail-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/
import { useState } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { Info, ExternalLink, AlertCircle, Loader2, Check, Users } from "lucide-react";
import { Info, ExternalLink, AlertCircle, Loader2, Check } from "lucide-react";
import { GitHubIcon } from "@/components/icons/github-icon";

import { BackButton } from "@/components/ui/back-button";
Expand All @@ -37,24 +37,100 @@ const COMPONENT_TYPE_DESCRIPTIONS: Record<string, string> = {
connector: "Connectors act as both an exporter and a receiver, joining two pipelines together.",
};

const STABILITY_DEFINITIONS: Record<string, { label: string; desc: string; color: string }> = {
development: {
label: "Development",
desc: "Not ready for general use. May have bugs or lack documentation.",
color: "text-blue-500",
},
alpha: {
label: "Alpha",
desc: "Ready for testing and early feedback. Not recommended for production.",
color: "text-purple-500",
},
beta: {
label: "Beta",
desc: "Ready for production use. Features are largely stable, though minor changes might occur.",
color: "text-green-500",
},
stable: {
label: "Stable",
desc: "Fully supported for production use. Backwards compatibility is guaranteed.",
color: "text-emerald-600 dark:text-emerald-500",
},
deprecated: {
label: "Deprecated",
desc: "Scheduled for removal. Should no longer be used.",
color: "text-red-500",
},
unmaintained: {
Comment on lines +51 to +66
label: "Unmaintained",
desc: "No longer actively maintained or supported.",
color: "text-red-600",
},
};

const getDistributionInfo = (distroName: string) => {
const lower = distroName.toLowerCase();
if (lower.includes("contrib")) {
return {
name: "OpenTelemetry Collector Contrib",
desc: "The community-driven distribution containing third-party plugins, specialized receivers, and experimental components.",
cmdLabel: "# Docker",
cmd: "docker pull otel/opentelemetry-collector-contrib:latest",
url: "https://github.com/open-telemetry/opentelemetry-collector-contrib",
};
}
if (lower.includes("core")) {
return {
name: "OpenTelemetry Collector Core",
desc: "The core distribution containing only the most essential, officially supported telemetry components.",
cmdLabel: "# Docker",
cmd: "docker pull otel/opentelemetry-collector:latest",
url: "https://github.com/open-telemetry/opentelemetry-collector",
};
}

if (lower === "k8s" || lower.includes("kubernetes")) {
return {
name: "OpenTelemetry Operator for Kubernetes",
desc: "The official Kubernetes Operator designed to manage and provision the OpenTelemetry Collector.",
cmdLabel: "# kubectl",
cmd: "kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml",
url: "https://github.com/open-telemetry/opentelemetry-operator",
};
}

return {
name: distroName,
desc: "A specialized OpenTelemetry distribution.",
cmdLabel: null,
cmd: null,
url: "https://opentelemetry.io/docs/collector/installation/",
};
};

const getBadgeVariant = (level: string): "success" | "info" | "warning" | "muted" => {
const lower = level.toLowerCase();
if (lower === "stable") return "success";
if (lower === "beta") return "info";
if (
lower === "alpha" ||
lower === "development" ||
lower === "deprecated" ||
lower === "unmaintained"
) {
return "warning";
}
return "muted";
};

export function CollectorDetailPage() {
const { version, id } = useParams<{ version: string; id: string }>();
const navigate = useNavigate();
const { data: component, loading, error } = useCollectorComponent(id ?? "", version ?? "");
const [activeTab, setActiveTab] = useState("details");

const getStabilityLabel = (level: string) => {
const labels: Record<string, string> = {
alpha: "Alpha",
beta: "Beta",
stable: "Stable",
deprecated: "Deprecated",
unmaintained: "Unmaintained",
development: "In Development",
};
return labels[level.toLowerCase()] || level;
};

if (loading) {
return (
<PageContainer>
Expand Down Expand Up @@ -105,6 +181,22 @@ export function CollectorDetailPage() {
);
}

const dynamicSignals = component.status?.stability
? Array.from(new Set(Object.values(component.status.stability).flat() as string[]))
: [];

const getSignalStability = (signalName: string) => {
if (!component.status?.stability) return null;
for (const [level, signals] of Object.entries(component.status.stability)) {
if ((signals as string[]).includes(signalName)) return level;
}
return null;
};

const activeStabilityLevels = component.status?.stability
? Object.keys(component.status.stability)
: [];

return (
<PageContainer>
<BackButton />
Expand Down Expand Up @@ -165,11 +257,6 @@ export function CollectorDetailPage() {
label: "Stability",
icon: <Check className="h-4 w-4" aria-hidden="true" />,
},
{
value: "owners",
label: "Ownership",
icon: <Users className="h-4 w-4" aria-hidden="true" />,
},
]}
/>
</div>
Expand Down Expand Up @@ -242,87 +329,144 @@ export function CollectorDetailPage() {

<TabsContent value="status" className="mt-0 p-6">
{component.status ? (
<div className="space-y-6">
<SectionHeader>Stability Levels</SectionHeader>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{Object.entries(component.status.stability).map(([level, signals]) => (
<DetailCard key={level} withHoverEffect>
<div className="space-y-3">
<GlowBadge
variant={
level === "stable" ? "success" : level === "beta" ? "info" : "warning"
}
className="text-xs capitalize"
>
{getStabilityLabel(level)}
</GlowBadge>
<div className="flex flex-wrap gap-2">
{signals.map((signal) => (
<span
<div className="space-y-10">
<div className="space-y-6">
<SectionHeader>Stability Levels</SectionHeader>
<div className="border-border/60 bg-card overflow-x-auto rounded-lg border shadow-sm">
<table className="w-full text-left text-sm">
<thead className="bg-muted/30">
<tr>
{dynamicSignals.map((signal) => (
<th
key={signal}
className="bg-muted/50 rounded px-2 py-1 text-sm font-medium"
scope="col"
className="border-border/60 text-muted-foreground border-b p-4 font-semibold capitalize"
>
{signal}
</span>
{signal.replace(/_/g, " ")}
</th>
Comment on lines +339 to +346
))}
</div>
</div>
</DetailCard>
))}
</tr>
</thead>
<tbody>
<tr className="divide-border/30 divide-x">
{dynamicSignals.map((signal) => {
const level = getSignalStability(signal);
return (
<td key={signal} className="p-4 align-top">
{level ? (
<GlowBadge
variant={getBadgeVariant(level)}
className="text-xs capitalize"
>
{level}
</GlowBadge>
) : (
<span className="text-muted-foreground/50 font-mono">-</span>
)}
</td>
);
})}
</tr>
</tbody>
</table>
</div>
</div>

<SectionHeader>Distribution Availability</SectionHeader>
<div className="flex flex-wrap gap-2">
{component.status.distributions.map((dist) => (
<GlowBadge key={dist} variant="muted" className="capitalize">
{dist}
</GlowBadge>
))}
</div>
</div>
) : (
<div className="py-12 text-center">
<AlertCircle className="text-muted-foreground/30 mx-auto h-12 w-12" />
<p className="text-muted-foreground mt-4">
No stability information available for this version.
</p>
</div>
)}
</TabsContent>
{activeStabilityLevels.length > 0 && (
<div className="space-y-4">
<h4 className="text-lg font-semibold">Stability Legend</h4>
<div className="grid gap-4 md:grid-cols-2">
{activeStabilityLevels.map((level) => {
const def = STABILITY_DEFINITIONS[level.toLowerCase()];
if (!def) return null;
return (
<div
key={level}
className="border-border/50 bg-card hover:bg-muted/20 rounded-lg border p-4 shadow-sm transition-colors"
>
<GlowBadge
variant={getBadgeVariant(level)}
className="w-fit text-xs capitalize"
>
{level}
</GlowBadge>
<p className="text-muted-foreground mt-2 text-sm">{def.desc}</p>
</div>
);
})}
</div>
</div>
)}

{/* Distribution Information */}
{component.status.distributions && component.status.distributions.length > 0 ? (
<div className="space-y-6">
<div>
<SectionHeader>Distribution Availability</SectionHeader>
<p className="text-muted-foreground mt-2 text-sm">
This component is packaged within the following distributions:
</p>
</div>

<TabsContent value="owners" className="mt-0 p-6">
{component.status?.codeowners?.active &&
component.status.codeowners.active.length > 0 ? (
<div className="space-y-6">
<SectionHeader>Code Owners</SectionHeader>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{component.status.codeowners.active.map((owner: string) => (
<DetailCard key={owner} withHoverEffect>
<div className="flex items-center gap-3">
<div className="bg-primary/10 flex h-10 w-10 items-center justify-center rounded-full">
<GitHubIcon className="text-primary h-5 w-5" />
</div>
<div>
<p className="text-sm font-semibold">{owner}</p>
<a
href={`https://github.com/${owner.replace("@", "")}`}
target="_blank"
rel="noopener noreferrer"
className="text-primary text-xs hover:underline"
<div className="grid gap-6 md:grid-cols-2">
{component.status.distributions.map((dist) => {
const distInfo = getDistributionInfo(dist);
return (
<div
key={dist}
className="border-border/60 bg-card flex flex-col justify-between rounded-lg border p-5 shadow-sm"
>
View profile
</a>
</div>
</div>
</DetailCard>
))}
</div>
<div>
<h3 className="mb-2 text-lg font-bold capitalize">
{distInfo.name}
</h3>
<p className="text-muted-foreground mb-4 text-sm">
{distInfo.desc}
</p>
</div>

<div className="mt-auto space-y-3">
{distInfo.cmd && (
<div className="bg-muted text-foreground overflow-x-auto rounded-md p-3 font-mono text-xs">
<span className="text-muted-foreground">
{distInfo.cmdLabel}
</span>
<br />
{distInfo.cmd}
</div>
)}
{distInfo.url && (
<a
href={distInfo.url}
target="_blank"
rel="noopener noreferrer"
className="text-primary inline-flex items-center gap-1 text-sm font-medium hover:underline"
>
View Documentation <ExternalLink className="h-3 w-3" />
</a>
)}
</div>
</div>
);
})}
</div>
</div>
) : (
<div className="space-y-6">
<SectionHeader>Distribution Availability</SectionHeader>
<div className="border-border/60 bg-card flex items-center justify-center rounded-lg border p-8 shadow-sm">
<p className="text-muted-foreground text-sm">
No distribution information is currently available for this component.
</p>
</div>
</div>
)}
</div>
) : (
<div className="py-12 text-center">
<Users className="text-muted-foreground/30 mx-auto h-12 w-12" />
<AlertCircle className="text-muted-foreground/30 mx-auto h-12 w-12" />
<p className="text-muted-foreground mt-4">
No code owner information found for this component.
No stability information available for this version.
</p>
</div>
)}
Expand Down
Loading