reopt designreopt design
DocsExploreToolsPricingBuilder
Start
Overview
Start
Next.js 설치
Private install
Core Concepts
아키텍처
Composition Patterns
Accessibility
Keyboard Patterns
Styling
Theme System
Advanced Patterns
Build & Operate
Skills
AI Integration
CLI (opt surface add)
Dependency Graph
Tools
Canvas Catalog
Theme Builder
Form Builder
Templates
Templates
Releases
Release Notes
Oopt-ui
reopt designreopt design

A design system for the AI era

  • Docs
  • Pricing
  • Releases
  • GitHub
  • Terms of Service
  • Privacy Policy

© 2026 reopt-ai. All rights reserved.

TestAnalytics

surface

다차원 테스트 품질 분석 대시보드. Summary cards + 6-tab multi-chart (Overview/Stability/Failures/Flaky/Performance/Advanced).

컴포넌트 의존 관계

깊이
▼ USES (4)TestAnalyticssummary-rowcontent-tabstime-range-selectorloading-overlay
100%

기본 사용

Test Analytics

Pass Rate94.2%↑ +2.1%
Total Tests1,234↑ +12
Total Runs56→
Flaky Tests12↓ -3
Test Analytics
Overview: test pass/fail trends chart

테스트 커버리지

2026년 2월 4일

생성된 테스트 결과를 찾지 못했습니다.

TestAnalytics 항목이 문서 메타에 연결되어 있지만 현재 생성 파일에는 없습니다.

테스트를 추가한 뒤 `bun run generate:test-results`를 실행하거나 `testDescribe` 매핑을 다시 확인하세요.

TestAnalytics Props

Prop타입기본값설명
stats*StatCardType[]—요약 통계 카드
activeTabTestAnalyticsTabId—활성 탭
onTabChange(tabId: TestAnalyticsTabId) => void—탭 변경 핸들러
renderOverview() => ReactNode—Overview 탭 렌더러
renderStability() => ReactNode—Stability 탭 렌더러
renderFailures() => ReactNode—Failures 탭 렌더러
renderFlaky() => ReactNode—Flaky 탭 렌더러
renderPerformance() => ReactNode—Performance 탭 렌더러
renderAdvanced() => ReactNode—Advanced 탭 렌더러
timeRangeDateRange—시간 범위 필터
onTimeRangeChange(range: DateRange) => void—시간 범위 변경 핸들러
loadingboolean—로딩 상태
headerReactNode—커스텀 헤더 슬롯
actionsReactNode—액션 버튼 슬롯
labelsTestAnalyticsLabels—커스텀 레이블
classNamestring—최외곽 CSS 클래스

Surface 설치

CLI가 공식 배포 채널입니다. 필요한 Surface를 프로젝트로 복사한 뒤 직접 수정할 수 있습니다.

bash
npx @reopt-ai/opt-cli surface add test-analytics

Consumer target

복사된 파일은 components/surfaces 아래에 저장됩니다.

tsx
import { TestAnalytics } from "@/components/surfaces/test-analytics";

Registry metadata

설명
다차원 테스트 품질 분석 대시보드. Summary cards + 6-tab multi-chart (Overview/Stability/Failures/Flaky/Performance/Advanced).
파일 수
1개
Registry dependencies
없음
Package dependencies
없음
태그
dashboardanalytics
Install notes
없음

포함 파일

  • test-analytics.tsx→test-analytics.tsx
Surface 소스 보기
test-analytics.tsx
"use client";

import * as React from "react";
import {
  SurfaceLayout,
  SummaryRow,
  TimeRangeSelector,
  cn,
  type DateRange,
  type StatCardType,
} from "@reopt-ai/opt-ui";

// ── Types ──

/** Type definition for `TestAnalyticsTabId`. */
export type TestAnalyticsTabId =
  | "overview"
  | "stability"
  | "failures"
  | "flaky"
  | "performance"
  | "advanced";

/** Labels for `TestAnalytics`. */
export interface TestAnalyticsLabels {
  title?: string;
  overviewTab?: string;
  stabilityTab?: string;
  failuresTab?: string;
  flakyTab?: string;
  performanceTab?: string;
  advancedTab?: string;
  emptyTitle?: string;
  emptyDescription?: string;
  refreshLabel?: string;
}

const defaultLabels: Required<TestAnalyticsLabels> = {
  title: "Test Analytics",
  overviewTab: "Overview",
  stabilityTab: "Stability",
  failuresTab: "Failures",
  flakyTab: "Flaky",
  performanceTab: "Performance",
  advancedTab: "Advanced",
  emptyTitle: "No analytics data",
  emptyDescription: "Run tests to generate analytics data.",
  refreshLabel: "Refresh",
};

// ── Props ──

/** Props for `TestAnalytics`. */
export interface TestAnalyticsProps {
  /** Summary stat cards (pass rate, tests, runs, flaky count, etc.) */
  stats: StatCardType[];
  /** Active tab */
  activeTab?: TestAnalyticsTabId;
  /** Called when tab changes */
  onTabChange?: (tabId: TestAnalyticsTabId) => void;
  /** Tab content renderers */
  renderOverview?: () => React.ReactNode;
  renderStability?: () => React.ReactNode;
  renderFailures?: () => React.ReactNode;
  renderFlaky?: () => React.ReactNode;
  renderPerformance?: () => React.ReactNode;
  renderAdvanced?: () => React.ReactNode;
  /** Time range for filtering data */
  timeRange?: DateRange;
  /** Called when time range changes */
  onTimeRangeChange?: (range: DateRange) => void;
  /** Refresh handler */
  onRefresh?: () => void;
  loading?: boolean;
  header?: React.ReactNode;
  actions?: React.ReactNode;
  labels?: TestAnalyticsLabels;
  className?: string;
}

// ── Component ──

/** Renders the `TestAnalytics` component. */
export function TestAnalytics({
  stats,
  activeTab: controlledActiveTab,
  onTabChange,
  renderOverview,
  renderStability,
  renderFailures,
  renderFlaky,
  renderPerformance,
  renderAdvanced,
  timeRange,
  onTimeRangeChange,
  onRefresh,
  loading = false,
  header,
  actions,
  labels: customLabels,
  className,
}: TestAnalyticsProps) {
  const labels = { ...defaultLabels, ...customLabels };
  const sectionId = React.useId();

  const [internalActiveTab, setInternalActiveTab] =
    React.useState<TestAnalyticsTabId>("overview");

  const activeTab = controlledActiveTab ?? internalActiveTab;

  const handleTabChange = React.useCallback(
    (tabId: string) => {
      setInternalActiveTab(tabId as TestAnalyticsTabId);
      onTabChange?.(tabId as TestAnalyticsTabId);
    },
    [onTabChange],
  );

  const isEmpty = stats.length === 0;

  const tabs = React.useMemo(
    () => [
      { id: "overview", label: labels.overviewTab },
      { id: "stability", label: labels.stabilityTab },
      { id: "failures", label: labels.failuresTab },
      { id: "flaky", label: labels.flakyTab },
      { id: "performance", label: labels.performanceTab },
      { id: "advanced", label: labels.advancedTab },
    ],
    [labels],
  );

  const tabRenderers: Record<
    TestAnalyticsTabId,
    (() => React.ReactNode) | undefined
  > = {
    overview: renderOverview,
    stability: renderStability,
    failures: renderFailures,
    flaky: renderFlaky,
    performance: renderPerformance,
    advanced: renderAdvanced,
  };

  return (
    <SurfaceLayout loading={loading} className={className}>
      {/* Header */}
      <div className="flex items-center justify-between">
        {header ? (
          <div>{header}</div>
        ) : (
          <h2 className="text-text-primary text-lg font-semibold">
            {labels.title}
          </h2>
        )}
        <div className="gap-element flex items-center">
          <TimeRangeSelector value={timeRange} onChange={onTimeRangeChange} />
          {onRefresh && (
            <button
              type="button"
              onClick={onRefresh}
              className="text-text-tertiary hover:text-text-primary rounded p-1.5 text-sm transition-colors"
              aria-label={labels.refreshLabel}
            >
              ↻
            </button>
          )}
          {actions}
        </div>
      </div>

      {isEmpty ? (
        <div
          role="status"
          className="flex flex-col items-center justify-center py-16 text-center"
        >
          <h3 className="text-text-primary text-lg font-medium">
            {labels.emptyTitle}
          </h3>
          <p className="text-text-tertiary mt-1 text-sm">
            {labels.emptyDescription}
          </p>
        </div>
      ) : (
        <>
          {/* Summary stat cards */}
          <SummaryRow stats={stats} columns={4} />

          {/* Tabbed content */}
          <section aria-labelledby={`${sectionId}-tabs`}>
            <span id={`${sectionId}-tabs`} className="sr-only">
              {labels.title}
            </span>
            <div
              role="tablist"
              aria-label={labels.title}
              className="border-border-subtle flex overflow-x-auto border-b"
            >
              {tabs.map((tab) => (
                <button
                  key={tab.id}
                  type="button"
                  role="tab"
                  aria-selected={activeTab === tab.id}
                  onClick={() => handleTabChange(tab.id)}
                  className={cn(
                    "px-4 py-2 text-sm font-medium transition-colors",
                    activeTab === tab.id
                      ? "border-accent text-text-primary border-b-2"
                      : "text-text-tertiary hover:text-text-secondary",
                  )}
                >
                  {tab.label}
                </button>
              ))}
            </div>
            <div role="tabpanel" className="py-4">
              {tabRenderers[activeTab]?.()}
            </div>
          </section>
        </>
      )}
    </SurfaceLayout>
  );
}

TestAnalytics.displayName = "TestAnalytics";