UnifiedLogs.fields.tsx 5.6 KB

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