Skip to content
Open
Show file tree
Hide file tree
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
13 changes: 2 additions & 11 deletions ecosystem-explorer/src/LegacyApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Header } from "@/components/layout/header";
import { Footer } from "@/components/layout/footer";
import { isEnabled } from "@/lib/feature-flags";
import { ErrorBoundary } from "@/components/ui/error-boundary";
import { Loader } from "@/components/ui/loader";

const HomePage = lazy(() =>
import("@/features/home/home-page").then((m) => ({ default: m.HomePage }))
Expand Down Expand Up @@ -82,17 +83,7 @@ export function LegacyApp() {
<Header />
<main className="flex-1 pt-16">
<ErrorBoundary>
<Suspense
fallback={
<div
className="flex min-h-[400px] items-center justify-center"
role="status"
aria-live="polite"
>
<div className="text-muted-foreground text-sm font-medium">Loading…</div>
</div>
}
>
<Suspense fallback={<Loader label="Loading…" />}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/java-agent" element={<JavaAgentPage />} />
Expand Down
74 changes: 74 additions & 0 deletions ecosystem-explorer/src/components/ui/loader.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { Loader } from "@/components/ui/loader";

describe("Loader Component", () => {
it("renders the large loader (lg) by default with accessibility attributes", () => {
const { container } = render(<Loader />);

const loaderContainer = screen.getByRole("status");
expect(loaderContainer).toBeInTheDocument();
expect(loaderContainer).toHaveAttribute("aria-live", "polite");
expect(loaderContainer).toHaveAttribute("aria-busy", "true");

// Large loader container classes
expect(loaderContainer).toHaveClass("flex", "min-h-[400px]", "flex-col", "items-center");

// Check spin icon
const svgIcon = container.querySelector(".animate-spin");
expect(svgIcon).toBeInTheDocument();
expect(svgIcon).toHaveAttribute("aria-hidden", "true");
});

it("renders large loader (lg) with a custom label and standard subtext", () => {
render(<Loader label="Fetching data..." />);

expect(screen.getByText("Fetching data...")).toBeInTheDocument();
expect(screen.getByText("This may take a moment")).toBeInTheDocument();
});

it("renders the small loader (sm) when specified", () => {
const { container } = render(<Loader size="sm" />);

const loaderContainer = screen.getByRole("status");
expect(loaderContainer).toBeInTheDocument();
expect(loaderContainer).toHaveAttribute("aria-live", "polite");
expect(loaderContainer).toHaveAttribute("aria-busy", "true");

// Small loader container classes
expect(loaderContainer).toHaveClass("flex", "items-center", "gap-3", "py-2");

const svgIcon = container.querySelector(".animate-spin");
expect(svgIcon).toBeInTheDocument();
expect(svgIcon).toHaveAttribute("aria-hidden", "true");
});

it("renders small loader (sm) with a custom label and no subtext", () => {
render(<Loader size="sm" label="Updating..." />);

expect(screen.getByText("Updating...")).toBeInTheDocument();
expect(screen.queryByText("This may take a moment")).not.toBeInTheDocument();
});

it("applies additional CSS classes when className is provided", () => {
render(<Loader className="custom-test-class" />);

const loaderContainer = screen.getByRole("status");
expect(loaderContainer).toHaveClass("custom-test-class");
});
});
62 changes: 62 additions & 0 deletions ecosystem-explorer/src/components/ui/loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Loader2 } from "lucide-react";

type LoaderProps = {
size?: "sm" | "lg";
className?: string;
label?: string;
};

export function Loader({ size = "lg", className = "", label }: LoaderProps) {
if (size === "sm") {
Comment on lines +24 to +25
return (
<div
className={`flex items-center gap-3 py-2 ${className}`}
role="status"
aria-live="polite"
aria-busy="true"
>
<Loader2 className="text-primary h-4 w-4 animate-spin" aria-hidden="true" />
{label && <span className="text-muted-foreground text-sm font-medium">{label}</span>}
</div>
);
}

return (
<div
className={`flex min-h-[400px] flex-col items-center justify-center space-y-6 px-4 py-12 text-center ${className}`}
role="status"
aria-live="polite"
aria-busy="true"
>
<div className="relative">
<div className="bg-primary/20 absolute inset-0 animate-pulse rounded-full blur-2xl" />

<div className="bg-card border-primary/10 relative inline-flex animate-pulse rounded-full border p-6 shadow-[0_0_50px_hsl(var(--primary-hsl)/0.15)]">
<Loader2 className="text-primary h-12 w-12 animate-spin" aria-hidden="true" />
</div>
</div>

{label && (
<div className="space-y-2">
<p className="text-foreground text-lg font-semibold tracking-tight">{label}</p>
<p className="text-muted-foreground text-sm">This may take a moment</p>
</div>
)}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
*/
import { useState } from "react";
import { useParams, useSearchParams, useNavigate } from "react-router-dom";
import { Info, ExternalLink, AlertCircle, Loader2, Check, Users } from "lucide-react";
import { Info, ExternalLink, AlertCircle, Check, Users } from "lucide-react";
import { Loader } from "@/components/ui/loader";
import { GitHubIcon } from "@/components/icons/github-icon";

import { BackButton } from "@/components/ui/back-button";
Expand Down Expand Up @@ -70,17 +71,7 @@ export function CollectorDetailPage() {
if (loading || versionLoading) {
return (
<PageContainer>
<div className="flex min-h-[400px] items-center justify-center">
<div className="text-center">
<div className="inline-flex animate-pulse rounded-full p-4 shadow-[0_0_60px_hsl(var(--otel-orange-hsl)/0.2)]">
<Loader2 className="text-secondary h-12 w-12 animate-spin" aria-hidden="true" />
</div>
<div className="mt-6 space-y-2">
<div className="text-foreground text-lg font-medium">Loading component...</div>
<div className="text-muted-foreground text-sm">This may take a moment</div>
</div>
</div>
</div>
<Loader label="Loading component..." />
</PageContainer>
);
}
Expand Down
20 changes: 3 additions & 17 deletions ecosystem-explorer/src/features/collector/collector-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Box,
ChevronDown,
ChevronRight,
Layers,
Loader2,
Plug,
Search,
Send,
Workflow,
} from "lucide-react";
import { Box, ChevronDown, ChevronRight, Layers, Plug, Search, Send, Workflow } from "lucide-react";
import { Loader } from "@/components/ui/loader";
import { useMemo, useState } from "react";
import { Link, useSearchParams, useParams } from "react-router-dom";

Expand Down Expand Up @@ -182,12 +173,7 @@ function CollectorPageInner() {
</div>

{componentsLoading ? (
<div className="flex flex-col items-center justify-center space-y-4 py-32">
<div className="inline-flex animate-pulse rounded-full p-4 shadow-[0_0_60px_hsl(var(--primary-hsl)/0.2)]">
<Loader2 className="text-primary h-10 w-10 animate-spin" aria-hidden="true" />
</div>
<p className="text-muted-foreground text-sm font-medium">Loading components...</p>
</div>
<Loader label="Loading components..." />
) : hasError ? (
<div className="flex flex-col items-center justify-center space-y-4 py-32">
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-red-100/10 text-red-500">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
* limitations under the License.
*/

import { AlertCircle, Loader2 } from "lucide-react";
import { AlertCircle } from "lucide-react";
import { Loader } from "@/components/ui/loader";
import type { VersionInfo } from "@/types/javaagent";
import { useTelemetryComparison } from "../../hooks/use-telemetry-comparison";
import { VersionSelectorPanel } from "./version-selector-panel";
Expand Down Expand Up @@ -70,10 +71,7 @@ export function TelemetryComparisonSection({
{/* Loading state */}
{loading && (
<div className="flex min-h-[300px] items-center justify-center">
<div className="flex items-center gap-3">
<Loader2 className="text-primary h-6 w-6 animate-spin" />
<p className="text-muted-foreground text-sm">Loading comparison data...</p>
</div>
<Loader size="sm" label="Loading comparison data..." />
</div>
)}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
import { useState, useCallback, useMemo, type JSX } from "react";
import type { InstrumentationData, InstrumentationModule } from "@/types/javaagent";
import { Loader } from "@/components/ui/loader";
import { useConfigurationBuilder } from "@/hooks/use-configuration-builder";
import {
useCustomizationStatusMap,
Expand Down Expand Up @@ -110,7 +111,7 @@ export function InstrumentationBrowser({
</header>

{loading ? (
<p className="text-muted-foreground text-sm">Loading instrumentations…</p>
<Loader size="sm" label="Loading instrumentations…" />
) : error ? (
<p className="text-sm text-red-400">Failed to load instrumentations.</p>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/
import { useEffect, useMemo, useRef, useState } from "react";
import { Loader } from "@/components/ui/loader";
import { BackButton } from "@/components/ui/back-button";
import { BetaBadge } from "@/components/ui/beta-badge";
import { PageContainer } from "@/components/layout/page-container";
Expand Down Expand Up @@ -355,8 +356,8 @@ export function ConfigurationBuilderPage() {
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsContent value="sdk">
{!schemaVersion || schema.loading || starter.loading ? (
<p className="text-muted-foreground mt-4 text-sm">Loading schema…</p>
) : schema.error ? (
<Loader size="sm" label="Loading schema…" className="mt-4" />
) : schema.error || !root ? (
<p className="mt-4 text-sm text-red-400">Failed to load schema.</p>
) : starter.error ? (
<p className="mt-4 text-sm text-red-400">Failed to load starter template.</p>
Expand All @@ -372,8 +373,8 @@ export function ConfigurationBuilderPage() {
</TabsContent>
<TabsContent value="instrumentation">
{!schemaVersion || schema.loading || starter.loading ? (
<p className="text-muted-foreground mt-4 text-sm">Loading schema…</p>
) : schema.error ? (
<Loader size="sm" label="Loading schema…" className="mt-4" />
) : schema.error || !root ? (
<p className="mt-4 text-sm text-red-400">Failed to load schema.</p>
) : starter.error ? (
<p className="mt-4 text-sm text-red-400">Failed to load starter template.</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ import {
Code,
Check,
AlertCircle,
Loader2,
HelpCircle,
} from "lucide-react";

import { Loader } from "@/components/ui/loader";

import { BackButton } from "@/components/ui/back-button";
import { Tabs, TabsContent } from "@/components/ui/tabs";
import { SegmentedTabList } from "@/components/ui/segmented-tabs";
Expand Down Expand Up @@ -112,22 +113,7 @@ export function InstrumentationDetailPage() {
if (loading) {
return (
<PageContainer>
<div className="flex min-h-[400px] items-center justify-center">
<div className="text-center">
<div
className="inline-flex animate-pulse rounded-full p-4"
style={{
boxShadow: "0 0 60px hsl(var(--otel-orange-hsl) / 0.2)",
}}
>
<Loader2 className="text-secondary h-12 w-12 animate-spin" aria-hidden="true" />
</div>
<div className="mt-6 space-y-2">
<div className="text-lg font-medium">Loading instrumentation...</div>
<div className="text-muted-foreground text-sm">This may take a moment</div>
</div>
</div>
</div>
<Loader label="Loading instrumentation..." />
</PageContainer>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
* limitations under the License.
*/
import { useState, useMemo, useEffect } from "react";
import { Search, Settings, Loader2 } from "lucide-react";
import { Search, Settings } from "lucide-react";
import { Loader } from "@/components/ui/loader";
import { BackButton } from "@/components/ui/back-button";
import { PageContainer } from "@/components/layout/page-container";
import { Tabs } from "@/components/ui/tabs";
Expand Down Expand Up @@ -127,22 +128,14 @@ export function JavaConfigurationListPage() {
<div className="mt-6">
<div className="text-muted-foreground mb-4 text-sm">
{isLoading
? "Loading..."
? null
: error
? "Unable to load configurations"
: `Found ${filteredConfigs.length} configurations`}
</div>

{isLoading ? (
<div className="flex min-h-[300px] items-center justify-center rounded-lg border border-dashed">
<div className="text-center">
<Loader2
className="text-primary mx-auto h-12 w-12 animate-spin"
aria-hidden="true"
/>
<p className="text-muted-foreground mt-4 text-sm">Loading configurations...</p>
</div>
</div>
<Loader label="Loading configurations..." />
) : error ? (
<div className="flex min-h-[300px] items-center justify-center rounded-lg border border-dashed">
<div className="mx-auto max-w-md px-4 text-center">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AlertCircle, Loader2 } from "lucide-react";
import { AlertCircle } from "lucide-react";
import { Loader } from "@/components/ui/loader";
import { BackButton } from "@/components/ui/back-button";
import { useVersions, useInstrumentations } from "@/hooks/use-javaagent-data";
import {
Expand Down Expand Up @@ -215,12 +216,7 @@ export function JavaInstrumentationListPage() {
<p className="text-muted-foreground">Please try refreshing the page.</p>
</div>
) : versionsLoading || instrumentationsLoading || (!resolvedVersion && !versionsError) ? (
<div className="flex flex-col items-center justify-center space-y-4 py-32">
<div className="inline-flex animate-pulse rounded-full p-4 shadow-[0_0_60px_hsl(var(--primary-hsl)/0.2)]">
<Loader2 className="text-primary h-10 w-10 animate-spin" aria-hidden="true" />
</div>
<p className="text-muted-foreground text-sm font-medium">Loading instrumentations...</p>
</div>
<Loader label="Loading instrumentations..." />
) : (
<>
<div className="border-border/50 flex items-center justify-between border-b pb-4">
Expand Down
Loading
Loading