UnifiedLogs.fields.tsx189 lines · main
1import { User } from 'lucide-react'
2import { cn } from 'ui'
3
4import { LOG_TYPES, METHODS, STATUS_CODE_LABELS } from './UnifiedLogs.constants'
5import { ColumnSchema } from './UnifiedLogs.schema'
6import { LogsMeta, SheetField } from './UnifiedLogs.types'
7import { getLevelLabel } from './UnifiedLogs.utils'
8import { LEVELS } from '@/components/ui/DataTable/DataTable.constants'
9import { DataTableFilterField, Option } from '@/components/ui/DataTable/DataTable.types'
10import { getLevelColor } from '@/components/ui/DataTable/DataTable.utils'
11import { useFormatDateTime } from '@/lib/datetime'
12
13const DateCell = (props: { date: ColumnSchema['date'] }) => {
14 const formatDateTime = useFormatDateTime()
15 const month = formatDateTime(props.date, 'MMM')
16 const day = formatDateTime(props.date, 'DD')
17 const year = formatDateTime(props.date, 'YYYY')
18 const time = formatDateTime(props.date, 'HH:mm:ss')
19
20 return (
21 <div className="font-mono whitespace-nowrap flex items-center gap-1 justify-end">
22 <span>{month}</span>
23 <span className="text-foreground/50">·</span>
24 <span>{day}</span>
25 <span className="text-foreground/50">·</span>
26 <span>{year}</span>
27 <span className="text-foreground/50">·</span>
28 <span>{time}</span>
29 </div>
30 )
31}
32
33// instead of filterFields, maybe just 'fields' with a filterDisabled prop?
34// that way, we could have 'message' or 'headers' field with label and value as well as type!
35export const filterFields = [
36 {
37 label: 'Time Range',
38 value: 'date',
39 type: 'timerange',
40 defaultOpen: true,
41 commandDisabled: true,
42 },
43 {
44 label: 'Log Type',
45 value: 'log_type',
46 type: 'checkbox',
47 defaultOpen: true,
48 options: LOG_TYPES.map((type) => ({ label: type, value: type })),
49 component: (props: Option) => {
50 return (
51 <div className="flex w-full items-center justify-between gap-2">
52 <span className="capitalize text-foreground/70 group-hover:text-accent-foreground text-xs">
53 {props.label.replace('_', ' ')}
54 </span>
55 </div>
56 )
57 },
58 },
59 {
60 label: 'Status',
61 value: 'status',
62 type: 'checkbox',
63 defaultOpen: true,
64 options: [],
65 hasDynamicOptions: true,
66 component: (props: Option) => {
67 if (typeof props.value === 'boolean') return null
68 if (typeof props.value === 'undefined') return null
69
70 const statusValue = String(props.value)
71 const statusLabel = STATUS_CODE_LABELS[statusValue as keyof typeof STATUS_CODE_LABELS]
72
73 return (
74 <div className="flex items-center gap-2 w-full min-w-0">
75 <span className="shrink-0 text-foreground">{statusValue}</span>
76 {statusLabel && (
77 <span className="text-[0.7rem] text-foreground-lighter truncate" title={statusLabel}>
78 {statusLabel}
79 </span>
80 )}
81 </div>
82 )
83 },
84 },
85 {
86 label: 'Level',
87 value: 'level',
88 type: 'checkbox',
89 defaultOpen: true,
90 options: LEVELS.map((level) => ({ label: level, value: level })),
91 component: (props: Option) => {
92 // TODO: type `Option` with `options` values via Generics
93 const value = props.value as (typeof LEVELS)[number]
94 return (
95 <div className="flex w-full max-w-28 items-center justify-between gap-2">
96 <span className="capitalize text-foreground/70 group-hover:text-accent-foreground text-xs">
97 {props.label}
98 </span>
99 <div className="flex items-center gap-2">
100 <div className={cn('h-2.5 w-2.5 rounded-[2px]', getLevelColor(value).bg)} />
101 <span className="text-xs text-muted-foreground/70">{getLevelLabel(value)}</span>
102 </div>
103 </div>
104 )
105 },
106 },
107 {
108 label: 'Method',
109 value: 'method',
110 type: 'checkbox',
111 defaultOpen: true,
112 options: METHODS.map((method) => ({ label: method, value: method })),
113 component: (props: Option) => {
114 return (
115 <span className="truncate block text-[0.75rem]" title={props.value as string}>
116 {props.value}
117 </span>
118 )
119 },
120 },
121 {
122 label: 'Pathname',
123 value: 'pathname',
124 type: 'checkbox',
125 defaultOpen: false,
126 options: [],
127 hasDynamicOptions: true,
128 hasAsyncSearch: false,
129 component: (props: Option) => {
130 return (
131 <span className="truncate block w-full text-[0.75rem]" title={props.value as string}>
132 {props.value}
133 </span>
134 )
135 },
136 },
137 {
138 label: 'Event message',
139 value: 'event_message',
140 type: 'checkbox',
141 defaultOpen: false,
142 options: [],
143 hasDynamicOptions: false,
144 hasAsyncSearch: false,
145 hidden: true,
146 component: (props: Option) => {
147 return (
148 <span className="truncate block w-full text-[0.75rem]" title={props.value as string}>
149 {props.value}
150 </span>
151 )
152 },
153 },
154] satisfies DataTableFilterField<ColumnSchema>[]
155
156export const sheetFields = [
157 {
158 id: 'id',
159 label: 'Request ID',
160 type: 'readonly',
161 skeletonClassName: 'w-64',
162 },
163 {
164 id: 'date',
165 label: 'Date',
166 type: 'timerange',
167 component: DateCell,
168 skeletonClassName: 'w-36',
169 },
170 {
171 id: 'auth_user',
172 label: 'Auth User',
173 type: 'readonly',
174 condition: (props) => Boolean(props.auth_user),
175 component: (props) => (
176 <div className="flex items-center gap-2">
177 <User size={14} className="text-foreground-lighter" />
178 <span className="font-mono">{props.auth_user}</span>
179 </div>
180 ),
181 skeletonClassName: 'w-56',
182 },
183 {
184 id: 'pathname',
185 label: 'Pathname',
186 type: 'input',
187 skeletonClassName: 'w-56',
188 },
189] satisfies SheetField<ColumnSchema, LogsMeta>[]