All files / src/app/admin/monitoring/slo page.tsx

0% Statements 0/138
100% Branches 0/0
0% Functions 0/1
0% Lines 0/138

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139                                                                                                                                                                                                                                                                                     
export const dynamic = 'force-dynamic';

import Link from 'next/link';
import { formatWindow } from '@/lib/observability';
import { getAllSLOsWithTrends } from '@/lib/observability/slo-history';
import { SLOTrend } from '@/components/features/admin/monitoring/SLOTrend';

/**
 * SLO Trends Page
 *
 * Displays all SLOs with their historical trend charts.
 */
export default async function SLOTrendsPage() {
  const slos = await getAllSLOsWithTrends();

  // Group SLOs by category
  const slosByCategory = slos.reduce(
    (acc, slo) => {
      if (!acc[slo.category]) {
        acc[slo.category] = [];
      }
      acc[slo.category].push(slo);
      return acc;
    },
    {} as Record<string, typeof slos>
  );

  const categoryOrder = ['overall', 'api', 'checkout', 'search'];

  return (
    <div className="p-6 min-h-screen bg-gray-50 dark:bg-gray-900">
      <div className="max-w-7xl mx-auto">
        {/* Header */}
        <div className="flex items-center justify-between mb-6">
          <div>
            <div className="flex items-center gap-4">
              <Link
                href="/admin/monitoring/observability"
                className="text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"
              >
                <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
                </svg>
              </Link>
              <h1 className="text-2xl font-bold text-gray-900 dark:text-white">
                SLO Historical Trends
              </h1>
            </div>
            <p className="text-gray-600 dark:text-gray-400 mt-1 ml-9">
              View historical performance and trends for all Service Level Objectives
            </p>
          </div>
        </div>

        {/* SLO Trend Charts by Category */}
        {categoryOrder.map((category) => {
          const categorySlos = slosByCategory[category];
          if (!categorySlos || categorySlos.length === 0) return null;

          return (
            <div key={category} className="mb-8">
              <h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
                {category === 'overall' ? 'Overall' : category.charAt(0).toUpperCase() + category.slice(1)} SLOs
              </h2>
              <div className="space-y-6">
                {categorySlos.map((slo) => {
                  // Convert trend data to the format expected by SLOTrend
                  const trendData = slo.trend.map((t) => ({
                    timestamp: t.timestamp,
                    value: t.value,
                    status: t.status,
                  }));

                  // Calculate summary if we have trend data
                  const summary = trendData.length > 0
                    ? {
                        avgValue: trendData.reduce((sum, t) => sum + t.value, 0) / trendData.length,
                        minValue: Math.min(...trendData.map((t) => t.value)),
                        maxValue: Math.max(...trendData.map((t) => t.value)),
                        breachCount: trendData.filter((t) => t.value < slo.target).length,
                        uptimePercentage:
                          ((trendData.length - trendData.filter((t) => t.value < slo.target).length) /
                            trendData.length) *
                          100,
                        dataPointCount: trendData.length,
                      }
                    : undefined;

                  return (
                    <Link
                      key={slo.name}
                      href={`/admin/monitoring/slo/${encodeURIComponent(slo.name)}`}
                      className="block hover:opacity-90 transition-opacity"
                    >
                      <SLOTrend
                        sloName={slo.name}
                        target={slo.target}
                        data={trendData}
                        summary={summary}
                        periodLabel={`Last 24 Hours | ${formatWindow(slo.window)} Window`}
                        height={250}
                      />
                    </Link>
                  );
                })}
              </div>
            </div>
          );
        })}

        {/* Empty State */}
        {Object.keys(slosByCategory).length === 0 && (
          <div className="bg-white dark:bg-gray-800 rounded-lg shadow p-12 text-center">
            <svg
              className="w-16 h-16 mx-auto text-gray-400 mb-4"
              fill="none"
              stroke="currentColor"
              viewBox="0 0 24 24"
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth={1.5}
                d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
              />
            </svg>
            <h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">
              No SLO Data Available
            </h3>
            <p className="text-gray-500 dark:text-gray-400">
              Historical SLO data will appear here once the snapshot cron job runs.
            </p>
          </div>
        )}
      </div>
    </div>
  );
}