Files
supabase/apps/studio/components/ui/DataTable/DataTableSheetRowAction.tsx
T
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

201 lines
6.0 KiB
TypeScript

import { Table } from '@tanstack/react-table'
import { endOfDay, endOfHour, startOfDay, startOfHour } from 'date-fns'
import {
CalendarClock,
CalendarDays,
CalendarSearch,
ChevronLeft,
ChevronRight,
Copy,
Equal,
Filter,
} from 'lucide-react'
import { ComponentPropsWithRef } from 'react'
import {
cn,
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from 'ui'
import { DataTableFilterField } from '@/components/ui/DataTable/DataTable.types'
import { useCopyToClipboard } from '@/hooks/ui/useCopyToClipboard'
interface DataTableSheetRowActionProps<
TData,
TFields extends DataTableFilterField<TData>,
> extends ComponentPropsWithRef<typeof DropdownMenuTrigger> {
fieldValue?: TFields['value']
filterFields: TFields[]
value: string | number
table: Table<TData>
label?: string
}
export function DataTableSheetRowAction<TData, TFields extends DataTableFilterField<TData>>({
fieldValue,
filterFields,
value,
children,
className,
table,
label,
onKeyDown,
...props
}: DataTableSheetRowActionProps<TData, TFields>) {
const { copy, isCopied } = useCopyToClipboard()
const field = !!fieldValue ? filterFields.find((f) => f.value === fieldValue) : undefined
const column =
!!fieldValue && !!field
? table.getAllColumns().find((c) => c.id === fieldValue.toString())
: undefined
function renderOptions() {
if (!field) return null
switch (field.type) {
case 'checkbox':
return (
<DropdownMenuItem
onClick={() => {
const filterValue = column?.getFilterValue() as undefined | Array<unknown>
const newValue = filterValue?.includes(value)
? filterValue
: [...(filterValue || []), value]
column?.setFilterValue(newValue)
}}
className="flex items-center gap-2"
>
<Filter size={12} />
Add as filter for {column?.id}
</DropdownMenuItem>
)
case 'input':
return (
<DropdownMenuItem
onClick={() => column?.setFilterValue(value)}
className="flex items-center gap-2"
>
<Filter size={12} />
Add as filter for {column?.id}
</DropdownMenuItem>
)
case 'slider':
return (
<DropdownMenuGroup>
<DropdownMenuItem
onClick={() => column?.setFilterValue([0, value])}
className="flex items-center gap-2"
>
{/* FIXME: change icon as it is not clear */}
<ChevronLeft size={12} />
Less or equal than
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => column?.setFilterValue([value, 5000])}
className="flex items-center gap-2"
>
{/* FIXME: change icon as it is not clear */}
<ChevronRight size={12} />
Greater or equal than
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => column?.setFilterValue([value])}
className="flex items-center gap-2"
>
<Equal size={12} />
Equal to
</DropdownMenuItem>
</DropdownMenuGroup>
)
case 'timerange':
const date = new Date(value)
return (
<DropdownMenuGroup>
<DropdownMenuItem
onClick={() => column?.setFilterValue([date])}
className="flex items-center gap-2"
>
<CalendarSearch size={12} />
Exact timestamp
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
const start = startOfHour(date)
const end = endOfHour(date)
column?.setFilterValue([start, end])
}}
className="flex items-center gap-2"
>
<CalendarClock size={12} />
Same hour
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
const start = startOfDay(date)
const end = endOfDay(date)
column?.setFilterValue([start, end])
}}
className="flex items-center gap-2"
>
<CalendarDays size={12} />
Same day
</DropdownMenuItem>
</DropdownMenuGroup>
)
default:
return null
}
}
return (
<DropdownMenu>
<DropdownMenuTrigger
className={cn(
'rounded-md ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
'relative',
className
)}
onKeyDown={(e) => {
if (e.key === 'ArrowDown') {
// REMINDER: default behavior is to open the dropdown menu
// But because we use it to navigate between rows, we need to prevent it
// and only use "Enter" to select the option
e.preventDefault()
}
onKeyDown?.(e)
}}
{...props}
>
{children}
{isCopied ? (
<div className="absolute inset-0 flex items-center justify-center rounded-md bg-surface-100/80 backdrop-blur-sm animate-in fade-in duration-150">
<span className="font-mono text-xs text-foreground-light">Copied</span>
</div>
) : null}
</DropdownMenuTrigger>
<DropdownMenuContent align="end" side="bottom" className="w-48 -translate-x-4">
{!!field && !!column && (
<>
{renderOptions()}
<DropdownMenuSeparator />
</>
)}
<DropdownMenuItem
onClick={() => copy(String(value), { timeout: 1000 })}
className="flex items-center gap-2"
>
<Copy size={12} />
Copy {label}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}