UnifiedLogs.utils.ts139 lines · main
1import { type Table as TTable } from '@tanstack/react-table'
2import { cn } from 'ui'
3
4import { FacetMetadataSchema } from './UnifiedLogs.schema'
5import { LEVELS } from '@/components/ui/DataTable/DataTable.constants'
6
7export const logEventBus = {
8 listeners: new Map<string, Set<(rowId: string) => void>>(),
9
10 on(event: 'selectTraceTab', callback: (rowId: string) => void) {
11 if (!this.listeners.has(event)) {
12 this.listeners.set(event, new Set())
13 }
14 this.listeners.get(event)?.add(callback)
15 return () => this.listeners.get(event)?.delete(callback)
16 },
17
18 emit(event: 'selectTraceTab', rowId: string) {
19 this.listeners.get(event)?.forEach((callback) => callback(rowId))
20 },
21}
22
23export const getFacetedUniqueValues = <TData>(facets?: Record<string, FacetMetadataSchema>) => {
24 return (_table: TTable<TData>, columnId: string) => {
25 return new Map(facets?.[columnId]?.rows?.map(({ value, total }) => [value, total]) || [])
26 }
27}
28
29export const getFacetedMinMaxValues = <TData>(facets?: Record<string, FacetMetadataSchema>) => {
30 return (_table: TTable<TData>, columnId: string) => {
31 const min = facets?.[columnId]?.min
32 const max = facets?.[columnId]?.max
33 if (typeof min === 'number' && typeof max === 'number') return [min, max]
34 if (typeof min === 'number') return [min, min]
35 if (typeof max === 'number') return [max, max]
36 return undefined
37 }
38}
39
40/**
41 * Returns a unified-logs row's timestamp in epoch milliseconds.
42 *
43 * The row mapper attaches a pre-parsed `date` (works for both BigQuery
44 * microsecond timestamps and OTEL ISO strings); fall back to the raw
45 * `timestamp` value when it's a number (older BQ-style microseconds).
46 */
47export function getRowTimestampMs(
48 row: { date?: Date | null; timestamp?: number | string | null } | null | undefined
49): number | null {
50 if (row?.date instanceof Date) return row.date.getTime()
51 if (typeof row?.timestamp === 'number') return row.timestamp / 1000
52 return null
53}
54
55export const getLevelLabel = (value: (typeof LEVELS)[number]): string => {
56 switch (value) {
57 case 'success':
58 return '2xx'
59 case 'warning':
60 return '4xx'
61 case 'error':
62 return '5xx'
63 }
64}
65
66// Helper function to determine level from HTTP status code
67export const getStatusLevel = (status?: number | string): string => {
68 if (!status) return 'success'
69 const statusNum = Number(status)
70 if (statusNum >= 500) return 'error'
71 if (statusNum >= 400) return 'warning'
72 if (statusNum >= 300) return 'info' // 3xx redirects are informational
73 if (statusNum >= 200) return 'success'
74 if (statusNum >= 100) return 'info'
75 return 'success'
76}
77
78export function getLevelRowClassName(value: (typeof LEVELS)[number]): string {
79 switch (value) {
80 case 'success':
81 return ''
82 case 'warning':
83 return cn(
84 'bg-warning/5 hover:bg-warning/10',
85 'data-[state=selected]:bg-warning/20 focus-visible:bg-warning/10',
86 'dark:bg-warning/10 dark:hover:bg-warning/20 dark:data-[state=selected]:bg-warning/30 dark:focus-visible:bg-warning/20'
87 )
88 case 'error':
89 return cn(
90 'bg-destructive/5 hover:bg-destructive/10',
91 'data-[state=selected]:bg-destructive/20 focus-visible:bg-destructive/10',
92 'dark:bg-error/10 dark:hover:bg-destructive/20 dark:data-[state=selected]:bg-destructive/30 dark:focus-visible:bg-destructive/20'
93 )
94 default:
95 return ''
96 }
97}
98
99/**
100 * Formats service type strings for display purposes
101 * Handles special cases like "edge function" -> "Edge Function"
102 * and applies proper capitalization to other service types
103 */
104export function formatServiceTypeForDisplay(serviceType: string): string {
105 if (!serviceType) return ''
106
107 // Handle special cases
108 const specialCases: Record<string, string> = {
109 'edge function': 'Edge Function',
110 postgrest: 'PostgREST',
111 postgres: 'Postgres',
112 auth: 'Auth',
113 storage: 'Storage',
114 }
115
116 return specialCases[serviceType.toLowerCase()] || serviceType
117}
118
119/**
120 * Parses an auth log event_message that may be a stringified JSON object.
121 * Auth log entries store metadata as JSON in event_message (e.g. {"msg":"...","level":"info"}).
122 * Extracts the human-readable msg field, falling back to error, then the raw string.
123 * The fallback ensures self-hosted versions with different formats still render correctly.
124 */
125export function parseAuthLogEventMessage(value: string | undefined): string | undefined {
126 if (!value) return value
127 try {
128 const parsed = JSON.parse(value)
129 if (parsed && typeof parsed === 'object') {
130 const msg = parsed.msg
131 const err = parsed.error
132 if (typeof msg === 'string' && msg.trim()) return msg
133 if (typeof err === 'string' && err.trim()) return err
134 }
135 return value
136 } catch {
137 return value
138 }
139}