mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 04:15:04 +03:00
## 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 --> [](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 -->
376 lines
13 KiB
TypeScript
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
|