Files
supabase/apps/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx
Gildas Garcia 243e079a2c chore: remove _Shadcn_ suffix from Command components (#46153)
## Problem

The `_Shadcn_` suffix isn't needed anymore on `Command` components

## Solution

- Remove the `_Shadcn_` suffix
- Simplify UI package exports
- Apply prettier

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

## Summary by CodeRabbit

* **Refactor**
* Simplified command component imports and exports across the UI library
by removing internal naming aliases and adopting direct component
references. Updated the public UI package barrel export to use wildcard
re-exports for cleaner API surface.

<!-- 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/46153?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 15:45:32 +02:00

376 lines
13 KiB
TypeScript

import { IS_PLATFORM, useFlag } from 'common'
import { BookOpen, Check, ChevronDown, ChevronsUpDown, Copy, ExternalLink, X } from 'lucide-react'
import Link from 'next/link'
import { ReactNode, useEffect, useState } from 'react'
import { logConstants } from 'shared-data'
import {
Badge,
Button,
cn,
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
copyToClipboard,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
Label,
Popover,
PopoverContent,
PopoverTrigger,
SidePanel,
Switch,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
import {
EXPLORER_DATEPICKER_HELPERS,
LOGS_SOURCE_DESCRIPTION,
LogsTableName,
} from './Logs.constants'
import { DatePickerValue, LogsDatePicker } from './Logs.DatePickers'
import { LogsWarning, LogTemplate } from './Logs.types'
import Table from '@/components/to-be-cleaned/Table'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { DOCS_URL } from '@/lib/constants'
export interface LogsQueryPanelProps {
templates?: LogTemplate[]
value: DatePickerValue
warnings: LogsWarning[]
onSelectTemplate: (template: LogTemplate) => void
onSelectSource: (source: string) => void
onDateChange: (value: DatePickerValue) => void
useOtel?: boolean
onUseOtelChange?: (value: boolean) => void
}
function DropdownMenuItemContent({ name, desc }: { name: ReactNode; desc?: string }) {
return (
<div className="grid gap-1">
<div className="font-mono font-bold">{name}</div>
{desc && <div className="text-foreground-light">{desc}</div>}
</div>
)
}
const LogsQueryPanel = ({
templates = [],
value,
warnings,
onSelectTemplate,
onSelectSource,
onDateChange,
useOtel = false,
onUseOtelChange,
}: LogsQueryPanelProps) => {
const [showReference, setShowReference] = useState(false)
const { logsTemplates } = useIsFeatureEnabled(['logs:templates'])
const showChToggleInLogExplorer = useFlag('showChToggleInLogExplorer')
const otelToggleEnabled = !!showChToggleInLogExplorer && !!onUseOtelChange
const {
projectAuthAll: authEnabled,
projectStorageAll: storageEnabled,
projectEdgeFunctionAll: edgeFunctionsEnabled,
} = useIsFeatureEnabled(['project_auth:all', 'project_storage:all', 'project_edge_function:all'])
const logsTableNames = Object.entries(LogsTableName)
.filter(([key]) => {
if (key === 'AUTH') return authEnabled
if (key === 'STORAGE') return storageEnabled
if (key === 'FN_EDGE') return edgeFunctionsEnabled
if (key === 'PG_CRON') return false
return true
})
.map(([, value]) => value)
const [selectedDatePickerValue, setSelectedDatePickerValue] = useState<DatePickerValue>(value)
useEffect(() => {
setSelectedDatePickerValue(value)
}, [value.from, value.to, value.text, value.isHelper])
const [open, setOpen] = useState(false)
const [selectedSchema, setSelectedSchema] = useState(logConstants.schemas[0])
return (
<div className="flex items-center border-b bg-surface-100 h-(--header-height)">
<div className="flex w-full items-center justify-between px-4 md:px-5 py-2 overflow-x-scroll no-scrollbar">
<div className="flex w-full flex-row items-center justify-between gap-x-4">
<div className="flex items-center gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button type="default" iconRight={<ChevronDown />}>
Insert source
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
side="bottom"
align="start"
className="max-h-[390px] overflow-auto"
>
{logsTableNames
.sort((a, b) => a.localeCompare(b))
.map((source) => (
<DropdownMenuItem key={source} onClick={() => onSelectSource(source)}>
<DropdownMenuItemContent
name={source}
desc={LOGS_SOURCE_DESCRIPTION[source]}
/>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
{IS_PLATFORM && logsTemplates && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button type="default" iconRight={<ChevronDown />}>
Templates
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="bottom" align="start">
{templates
.sort((a, b) => a.label!.localeCompare(b.label!))
.map((template) => (
<DropdownMenuItem
key={template.label}
onClick={() => onSelectTemplate(template)}
>
<p>{template.label}</p>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)}
<LogsDatePicker
value={selectedDatePickerValue}
onSubmit={(value) => {
setSelectedDatePickerValue(value)
onDateChange(value)
}}
helpers={EXPLORER_DATEPICKER_HELPERS}
/>
{otelToggleEnabled && (
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center gap-2">
<Switch
id="logs-explorer-otel-toggle"
checked={useOtel}
onCheckedChange={(checked) => onUseOtelChange?.(checked)}
/>
<Label
htmlFor="logs-explorer-otel-toggle"
className="text-xs text-foreground-light cursor-pointer"
>
OTEL endpoint
</Label>
</div>
</TooltipTrigger>
<TooltipContent side="bottom" className="max-w-xs">
Run this query against the new ClickHouse-backed OTEL endpoint instead of
BigQuery. Use to validate ClickHouse SQL before relying on it.
</TooltipContent>
</Tooltip>
)}
<div
data-testid="log-explorer-warnings"
className={`transition-all duration-300 h-full ${
warnings.length > 0 ? 'opacity-100' : 'invisible h-0 w-0 opacity-0'
}`}
>
<Tooltip>
<TooltipTrigger className="flex items-start">
<Badge variant="warning">
{warnings.length} {warnings.length > 1 ? 'warnings' : 'warning'}
</Badge>
<TooltipContent className="p-0 divide-y max-w-xs" side="bottom">
{warnings.map((warning, index) => (
<p
key={index}
className="px-3 py-1.5 text-xs text-foreground-light text-left"
>
{warning.text}{' '}
{warning.link && (
<Link href={warning.link}>{warning.linkText || 'View'}</Link>
)}
</p>
))}
</TooltipContent>
</TooltipTrigger>
</Tooltip>
</div>
</div>
<SidePanel
size="large"
header={
<div className="flex flex-row justify-between items-center">
<h3>Field Reference</h3>
<Button
type="text"
className="px-1"
onClick={() => setShowReference(false)}
icon={<X />}
/>
</div>
}
visible={showReference}
cancelText="Close"
onCancel={() => setShowReference(false)}
hideFooter
triggerElement={
<Button
type="text"
onClick={() => setShowReference(true)}
icon={<BookOpen />}
className="px-2"
>
<span>Field Reference</span>
</Button>
}
>
<SidePanel.Content>
<div className="pt-4 pb-2 space-y-1">
<p className="text-sm">
The following table shows all the available paths that can be queried from each
respective source. Do note that to access nested keys, you would need to perform
the necessary{' '}
<Link
href={`${DOCS_URL}/guides/platform/logs#unnesting-arrays`}
target="_blank"
rel="noreferrer"
className="text-brand"
>
unnesting joins
<ExternalLink
size="14"
className="ml-1 inline translate-y-[-2px]"
strokeWidth={1.5}
/>
</Link>
</p>
</div>
</SidePanel.Content>
<SidePanel.Separator />
<div className="px-4 pb-4 flex flex-col gap-4">
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
type="default"
role="combobox"
size={'small'}
aria-expanded={open}
className="w-full justify-between"
iconRight={<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />}
>
{value ? selectedSchema?.name : 'Select source...'}
</Button>
</PopoverTrigger>
<PopoverContent className="p-0" sameWidthAsTrigger>
<Command>
<CommandInput placeholder="Search source..." />
<CommandList>
<CommandEmpty>No source found.</CommandEmpty>
<CommandGroup>
{logConstants.schemas.map((schema) => (
<CommandItem
key={schema.reference}
value={schema.reference}
onSelect={() => {
setSelectedSchema(schema)
setOpen(false)
}}
>
<Check
className={cn(
'mr-2 h-4 w-4',
selectedSchema === schema ? 'opacity-100' : 'opacity-0'
)}
/>
{schema.name}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
<Table
head={[
<Table.th className="text-xs p-2!" key="path">
Path
</Table.th>,
<Table.th key="type" className="text-xs p-2!">
Type
</Table.th>,
]}
body={selectedSchema.fields.map((field) => (
<Field key={field.path} field={field} />
))}
/>
</div>
</SidePanel>
</div>
</div>
</div>
)
}
const Field = ({
field,
}: {
field: {
path: string
type: string
}
}) => {
const [isCopied, setIsCopied] = useState(false)
return (
<Table.tr>
<Table.td
className="font-mono text-xs p-2! cursor-pointer hover:text-foreground transition flex items-center space-x-2"
onClick={() =>
copyToClipboard(field.path, () => {
setIsCopied(true)
setTimeout(() => setIsCopied(false), 3000)
})
}
>
<span>{field.path}</span>
{isCopied ? (
<Tooltip>
<TooltipTrigger>
<Check size={14} strokeWidth={3} className="text-brand" />
</TooltipTrigger>
<TooltipContent side="bottom">Copied</TooltipContent>
</Tooltip>
) : (
<Tooltip>
<TooltipTrigger>
<Copy size={14} strokeWidth={1.5} />
</TooltipTrigger>
<TooltipContent side="bottom">Copy value</TooltipContent>
</Tooltip>
)}
</Table.td>
<Table.td className="font-mono text-xs p-2!">{field.type}</Table.td>
</Table.tr>
)
}
export default LogsQueryPanel