Files
supabase/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.fields.tsx
Joshen Lim e8af602d2d Joshen/debug 110 default unified logs search to event message (#46134)
## Context

Adjusts the filter bar component to accept a `freeformDefaultProperty`,
in which entering any text in the filter bar will opt to search against
that property by default. (Property must be defined within the
`filterProperties` prop too)

Applies to unified logs, which for e.g "Event message" is a valid
filter:
<img width="428" height="250" alt="image"
src="https://github.com/user-attachments/assets/a08c015c-c9aa-4985-9e15-6429f455ccf0"
/>

I've set `freeformDefaultProperty` to be `event_message`, and hence
typing free text will opt the default action to just filtering against
that property
<img width="437" height="135" alt="image"
src="https://github.com/user-attachments/assets/ccf6944a-1c5d-4944-acfa-ac82dc20bb10"
/>

### Demo:


https://github.com/user-attachments/assets/287ee22d-f957-48e5-89b0-1fed159cd86a

### Other changes
- Opt to deprecate `truncateText` util from unified logs -> preference
for tailwind instead
- Event message is added as a filter field, but hidden in the side nav
(wouldn't make sense since the content is very dynamic, unlike something
similar like pathname)
- Fixes some console errors in unified logs because we were using
`.getColumn` in `DataTableSheetRowAction`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added an "Event message" filter (hidden by default) and set it as the
default target for freeform searches.

* **UI Improvements**
  * Filter sidebar resize constraints tightened.
  * Timeline chart spacing and x-axis tick behavior improved.
* Action labels and command list items no longer truncate long input
values.

* **Bug Fixes**
* More reliable resolution of target columns for row actions, improving
available filter options.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46134?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-20 20:44:31 +07:00

190 lines
5.6 KiB
TypeScript

import { User } from 'lucide-react'
import { cn } from 'ui'
import { LOG_TYPES, METHODS, STATUS_CODE_LABELS } from './UnifiedLogs.constants'
import { ColumnSchema } from './UnifiedLogs.schema'
import { LogsMeta, SheetField } from './UnifiedLogs.types'
import { getLevelLabel } from './UnifiedLogs.utils'
import { LEVELS } from '@/components/ui/DataTable/DataTable.constants'
import { DataTableFilterField, Option } from '@/components/ui/DataTable/DataTable.types'
import { getLevelColor } from '@/components/ui/DataTable/DataTable.utils'
import { useFormatDateTime } from '@/lib/datetime'
const DateCell = (props: { date: ColumnSchema['date'] }) => {
const formatDateTime = useFormatDateTime()
const month = formatDateTime(props.date, 'MMM')
const day = formatDateTime(props.date, 'DD')
const year = formatDateTime(props.date, 'YYYY')
const time = formatDateTime(props.date, 'HH:mm:ss')
return (
<div className="font-mono whitespace-nowrap flex items-center gap-1 justify-end">
<span>{month}</span>
<span className="text-foreground/50">·</span>
<span>{day}</span>
<span className="text-foreground/50">·</span>
<span>{year}</span>
<span className="text-foreground/50">·</span>
<span>{time}</span>
</div>
)
}
// instead of filterFields, maybe just 'fields' with a filterDisabled prop?
// that way, we could have 'message' or 'headers' field with label and value as well as type!
export const filterFields = [
{
label: 'Time Range',
value: 'date',
type: 'timerange',
defaultOpen: true,
commandDisabled: true,
},
{
label: 'Log Type',
value: 'log_type',
type: 'checkbox',
defaultOpen: true,
options: LOG_TYPES.map((type) => ({ label: type, value: type })),
component: (props: Option) => {
return (
<div className="flex w-full items-center justify-between gap-2">
<span className="capitalize text-foreground/70 group-hover:text-accent-foreground text-xs">
{props.label.replace('_', ' ')}
</span>
</div>
)
},
},
{
label: 'Status',
value: 'status',
type: 'checkbox',
defaultOpen: true,
options: [],
hasDynamicOptions: true,
component: (props: Option) => {
if (typeof props.value === 'boolean') return null
if (typeof props.value === 'undefined') return null
const statusValue = String(props.value)
const statusLabel = STATUS_CODE_LABELS[statusValue as keyof typeof STATUS_CODE_LABELS]
return (
<div className="flex items-center gap-2 w-full min-w-0">
<span className="shrink-0 text-foreground">{statusValue}</span>
{statusLabel && (
<span className="text-[0.7rem] text-foreground-lighter truncate" title={statusLabel}>
{statusLabel}
</span>
)}
</div>
)
},
},
{
label: 'Level',
value: 'level',
type: 'checkbox',
defaultOpen: true,
options: LEVELS.map((level) => ({ label: level, value: level })),
component: (props: Option) => {
// TODO: type `Option` with `options` values via Generics
const value = props.value as (typeof LEVELS)[number]
return (
<div className="flex w-full max-w-28 items-center justify-between gap-2">
<span className="capitalize text-foreground/70 group-hover:text-accent-foreground text-xs">
{props.label}
</span>
<div className="flex items-center gap-2">
<div className={cn('h-2.5 w-2.5 rounded-[2px]', getLevelColor(value).bg)} />
<span className="text-xs text-muted-foreground/70">{getLevelLabel(value)}</span>
</div>
</div>
)
},
},
{
label: 'Method',
value: 'method',
type: 'checkbox',
defaultOpen: true,
options: METHODS.map((method) => ({ label: method, value: method })),
component: (props: Option) => {
return (
<span className="truncate block text-[0.75rem]" title={props.value as string}>
{props.value}
</span>
)
},
},
{
label: 'Pathname',
value: 'pathname',
type: 'checkbox',
defaultOpen: false,
options: [],
hasDynamicOptions: true,
hasAsyncSearch: false,
component: (props: Option) => {
return (
<span className="truncate block w-full text-[0.75rem]" title={props.value as string}>
{props.value}
</span>
)
},
},
{
label: 'Event message',
value: 'event_message',
type: 'checkbox',
defaultOpen: false,
options: [],
hasDynamicOptions: false,
hasAsyncSearch: false,
hidden: true,
component: (props: Option) => {
return (
<span className="truncate block w-full text-[0.75rem]" title={props.value as string}>
{props.value}
</span>
)
},
},
] satisfies DataTableFilterField<ColumnSchema>[]
export const sheetFields = [
{
id: 'id',
label: 'Request ID',
type: 'readonly',
skeletonClassName: 'w-64',
},
{
id: 'date',
label: 'Date',
type: 'timerange',
component: DateCell,
skeletonClassName: 'w-36',
},
{
id: 'auth_user',
label: 'Auth User',
type: 'readonly',
condition: (props) => Boolean(props.auth_user),
component: (props) => (
<div className="flex items-center gap-2">
<User size={14} className="text-foreground-lighter" />
<span className="font-mono">{props.auth_user}</span>
</div>
),
skeletonClassName: 'w-56',
},
{
id: 'pathname',
label: 'Pathname',
type: 'input',
skeletonClassName: 'w-56',
},
] satisfies SheetField<ColumnSchema, LogsMeta>[]