mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
<img width="1454" height="786" alt="image" src="https://github.com/user-attachments/assets/995790b1-a242-4a61-a0a0-349de7234923" /> Stack 4/4 · previous: #50688 ## Problem Selecting several logs to copy or send to the Assistant meant using a separate checkbox column and a banner above the table. Clicking a row and checking a row were two different selections. ## Solution - Unified Logs uses `useTableRowSelection`, so a row click, Cmd/Ctrl-click, Shift-click, and the checkbox all act on one selection. The selection resets when the project or filters change, and a linked `?id=` still restores its log. - The level indicator and checkbox now share one column. The checkbox shows on hover, focus, or when the row is selected. - The detail panel shows every selected log: one log gets the tabs, and two or more show their combined JSON. Copy-as-JSON and "Explain with AI" move into the panel header (`LogSelectionActions`), replacing `RowSelectionHeader`. - New Shift+↑ / Shift+↓ shortcuts extend the selection, with a hint in the panel footer. Prev/next navigation follows display order and keeps the row in view. - `LogTypeIcon` no longer puts a nested button inside clickable rows. ## Review instructions 1. Click a log, then Shift-click another. The range should be selected and the panel should show the combined JSON. 2. Cmd-click to add or remove single logs. Use the copy button (or its shortcut) and "Explain with AI". 3. With the panel open, press Shift+↑ / Shift+↓ to grow and shrink the selection. 4. Change a filter. The selection should clear. ## Checklist - [ ] I have read [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Summary * **New Features** * Added multi-row selection in Unified Logs, including additive, range, checkbox, and keyboard selection. * Added bulk JSON viewing and copying, plus AI-assisted actions for selected logs. * Added Shift+Arrow shortcuts to extend selections and improved row navigation. * **Accessibility** * Improved labels and focus behavior for log controls and log type indicators. * **Bug Fixes** * Kept single-log Overview and Raw JSON views available alongside multi-log selection. * Applied metadata visibility settings to selected-log actions and disabled actions for invalid log data. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
129 lines
4.7 KiB
TypeScript
129 lines
4.7 KiB
TypeScript
import { useState } from 'react'
|
|
import {
|
|
ResizableHandle,
|
|
ResizablePanel,
|
|
Tabs,
|
|
TabsContent,
|
|
TabsIndicator,
|
|
TabsList,
|
|
TabsTrigger,
|
|
} from 'ui'
|
|
import { CodeBlock } from 'ui-patterns/CodeBlock'
|
|
|
|
import { LogDetail } from './components/LogDetail'
|
|
import { LogLevelDot } from './components/LogLevelDot'
|
|
import { LogSelectionActions } from './LogSelectionActions'
|
|
import { ServiceFlowPanelControls } from './ServiceFlow/components/ServiceFlowPanelControls'
|
|
import { getLogDataForMetadataVisibility } from './ServiceFlowPanel.utils'
|
|
import { ColumnSchema } from './UnifiedLogs.schema'
|
|
import { QuerySearchParamsType } from './UnifiedLogs.types'
|
|
import { getEventMessageDisplay, getRawLogData } from './UnifiedLogs.utils'
|
|
import { ShortcutBadge } from '@/components/ui/ShortcutBadge'
|
|
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
|
|
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
|
|
|
|
interface ServiceFlowPanelProps {
|
|
dock: 'bottom' | 'right'
|
|
setDock: (value: 'bottom' | 'right') => void
|
|
selectedRows: ColumnSchema[]
|
|
searchParameters: QuerySearchParamsType
|
|
}
|
|
|
|
export function ServiceFlowPanel({
|
|
dock,
|
|
setDock,
|
|
selectedRows,
|
|
searchParameters,
|
|
}: ServiceFlowPanelProps) {
|
|
const [activeTab, setActiveTab] = useState('overview')
|
|
const hasMultiple = selectedRows.length > 1
|
|
const selectedRow = selectedRows[0]
|
|
const title = hasMultiple
|
|
? `${selectedRows.length} logs selected`
|
|
: getEventMessageDisplay(selectedRow?.log_type ?? '', selectedRow?.event_message).message ||
|
|
selectedRow?.id
|
|
const { logsMetadata } = useIsFeatureEnabled(['logs:metadata'])
|
|
const selectedJson = selectedRows.map((row) =>
|
|
getLogDataForMetadataVisibility(getRawLogData(row), logsMetadata)
|
|
)
|
|
|
|
return (
|
|
<>
|
|
<ResizableHandle withHandle />
|
|
<ResizablePanel
|
|
id="log-sidepanel"
|
|
defaultSize={400}
|
|
minSize={dock === 'bottom' ? 300 : 400}
|
|
className="bg-dash-sidebar"
|
|
>
|
|
<div className="flex h-full flex-col overflow-hidden">
|
|
<div className="flex min-w-0 items-center gap-6 px-4 py-2">
|
|
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
{!hasMultiple && (
|
|
<div className="flex h-4 w-4 shrink-0 items-center justify-center">
|
|
<LogLevelDot level={selectedRow?.level} />
|
|
</div>
|
|
)}
|
|
<span
|
|
className="min-w-0 flex-1 truncate heading-meta text-foreground"
|
|
role="status"
|
|
title={title}
|
|
>
|
|
{title}
|
|
</span>
|
|
</div>
|
|
<div className="flex shrink-0 items-center gap-1">
|
|
<LogSelectionActions rows={selectedRows} />
|
|
<ServiceFlowPanelControls dock={dock} setDock={setDock} />
|
|
</div>
|
|
</div>
|
|
{hasMultiple ? (
|
|
<div
|
|
className="min-h-0 flex-1 overflow-auto"
|
|
role="region"
|
|
aria-label="Selected logs JSON"
|
|
>
|
|
<CodeBlock
|
|
language="json"
|
|
hideCopy
|
|
wrapperClassName="!overflow-visible bg-surface-100/50 [&_pre]:!bg-surface-100/50"
|
|
className="rounded-none border-none !overflow-x-visible [&_code]:!leading-tight [&_pre]:!leading-tight"
|
|
>
|
|
{JSON.stringify(selectedJson, null, 2)}
|
|
</CodeBlock>
|
|
</div>
|
|
) : (
|
|
<Tabs
|
|
value={activeTab}
|
|
onValueChange={setActiveTab}
|
|
className="flex min-h-0 flex-1 flex-col"
|
|
>
|
|
<TabsList className="shrink-0 gap-x-4 px-4">
|
|
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
<TabsTrigger value="raw-json">Raw JSON</TabsTrigger>
|
|
<TabsIndicator />
|
|
</TabsList>
|
|
{['overview', 'raw-json'].map((tab) => (
|
|
<TabsContent
|
|
key={`${tab}-${selectedRow?.id}`}
|
|
value={tab}
|
|
className="mt-0 min-h-0 flex-1 overflow-auto"
|
|
>
|
|
{selectedRow && (
|
|
<LogDetail row={selectedRow} tab={tab} searchParameters={searchParameters} />
|
|
)}
|
|
</TabsContent>
|
|
))}
|
|
</Tabs>
|
|
)}
|
|
<div className="flex shrink-0 flex-wrap items-center gap-2 border-t px-4 py-2 text-xs text-foreground-lighter">
|
|
<ShortcutBadge shortcutId={SHORTCUT_IDS.UNIFIED_LOGS_EXTEND_PREV_ROW} />
|
|
<ShortcutBadge shortcutId={SHORTCUT_IDS.UNIFIED_LOGS_EXTEND_NEXT_ROW} />
|
|
<span>Extend selection</span>
|
|
</div>
|
|
</div>
|
|
</ResizablePanel>
|
|
</>
|
|
)
|
|
}
|