All files / src/components/features/admin/monitoring/RecentRumEvents index.tsx

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

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 140 141 142 143                                                                                                                                                                                                                                                                                             
'use client';

import React from 'react';

export interface RumEventSummary {
  type: string;
  _count: number;
}

export interface RecentRumEventsProps {
  /** RUM event summaries grouped by type */
  events: RumEventSummary[];
}

const EVENT_TYPE_CONFIG: Record<
  string,
  { label: string; color: string; description: string }
> = {
  pageview: {
    label: 'Page Views',
    color: 'bg-blue-500',
    description: 'User page navigation events',
  },
  vitals: {
    label: 'Web Vitals',
    color: 'bg-green-500',
    description: 'Core Web Vitals measurements',
  },
  api_call: {
    label: 'API Calls',
    color: 'bg-purple-500',
    description: 'Client-side API requests',
  },
  error: {
    label: 'Errors',
    color: 'bg-red-500',
    description: 'Client-side JavaScript errors',
  },
  long_task: {
    label: 'Long Tasks',
    color: 'bg-yellow-500',
    description: 'Tasks blocking the main thread >50ms',
  },
  event: {
    label: 'Custom Events',
    color: 'bg-indigo-500',
    description: 'Custom tracking events',
  },
};

/**
 * RecentRumEvents Component
 *
 * Displays a summary of Real User Monitoring events by type.
 */
export function RecentRumEvents({ events }: RecentRumEventsProps) {
  const totalEvents = events.reduce((sum, e) => sum + e._count, 0);

  // Sort events by count descending
  const sortedEvents = [...events].sort((a, b) => b._count - a._count);

  return (
    <div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-8" data-testid="rum-events">
      <div className="flex justify-between items-center mb-4">
        <h2 className="text-lg font-semibold text-gray-900 dark:text-white">
          Real User Monitoring (24h)
        </h2>
        <span className="text-sm text-gray-500 dark:text-gray-400">
          {totalEvents.toLocaleString()} total events
        </span>
      </div>

      {events.length > 0 ? (
        <div className="space-y-4">
          {/* Event type breakdown */}
          <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
            {sortedEvents.map((event) => {
              const config = EVENT_TYPE_CONFIG[event.type] || {
                label: event.type,
                color: 'bg-gray-500',
                description: 'Unknown event type',
              };
              const percentage = totalEvents > 0 ? (event._count / totalEvents) * 100 : 0;

              return (
                <div
                  key={event.type}
                  className="p-3 bg-gray-50 dark:bg-gray-700 rounded-lg"
                  title={config.description}
                >
                  <div className="flex items-center mb-2">
                    <div className={`w-3 h-3 rounded-full ${config.color} mr-2`} />
                    <span className="text-xs font-medium text-gray-600 dark:text-gray-300 truncate">
                      {config.label}
                    </span>
                  </div>
                  <p className="text-lg font-bold text-gray-900 dark:text-white">
                    {event._count.toLocaleString()}
                  </p>
                  <p className="text-xs text-gray-500 dark:text-gray-400">
                    {percentage.toFixed(1)}%
                  </p>
                </div>
              );
            })}
          </div>

          {/* Visual bar chart */}
          <div className="mt-4">
            <div className="flex h-4 rounded-full overflow-hidden bg-gray-200 dark:bg-gray-700">
              {sortedEvents.map((event) => {
                const config = EVENT_TYPE_CONFIG[event.type] || { color: 'bg-gray-500' };
                const percentage = totalEvents > 0 ? (event._count / totalEvents) * 100 : 0;
                if (percentage < 1) return null;

                return (
                  <div
                    key={event.type}
                    className={`${config.color} transition-all duration-300`}
                    style={{ width: `${percentage}%` }}
                    title={`${event.type}: ${event._count} events (${percentage.toFixed(1)}%)`}
                  />
                );
              })}
            </div>
          </div>
        </div>
      ) : (
        <div className="text-center py-8">
          <p className="text-gray-500 dark:text-gray-400">
            No RUM events recorded in the last 24 hours
          </p>
          <p className="text-sm text-gray-400 dark:text-gray-500 mt-2">
            Enable RUM by setting NEXT_PUBLIC_RUM_ENABLED=true
          </p>
        </div>
      )}
    </div>
  );
}

export default RecentRumEvents;