Files
supabase/apps/studio/components/interfaces/UnifiedLogs/LogSelectionActions.tsx
T
Saxon FletcherandClaude Opus 5 9b12165400 feat(studio): multi-select logs with click, shift, and keyboard ranges (#50689)
<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>
2026-09-25 19:03:01 +10:00

77 lines
2.8 KiB
TypeScript

import { useRef } from 'react'
import { AiIconAnimation } from 'ui'
import { buildLogsPrompt, formatLogsAsJson } from '../Settings/Logs/Logs.utils'
import { parseSelectedLogs } from './LogSelectionActions.utils'
import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import CopyButton from '@/components/ui/CopyButton'
import { Shortcut } from '@/components/ui/Shortcut'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { useTrack } from '@/lib/telemetry/track'
import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state'
export const LogSelectionActions = ({ rows }: { rows: unknown[] }) => {
const { openSidebar } = useSidebarManagerSnapshot()
const aiSnap = useAiAssistantStateSnapshot()
const track = useTrack()
const copyButtonRef = useRef<HTMLButtonElement>(null)
const { logsMetadata } = useIsFeatureEnabled(['logs:metadata'])
const selectedLogs = parseSelectedLogs(rows, logsMetadata)
const selectedRows = selectedLogs.success ? selectedLogs.data : []
const hasValidSelection = selectedRows.length > 0
const handleOpenAiAssistant = () => {
if (!hasValidSelection) return
const prompt = buildLogsPrompt(selectedRows)
openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
aiSnap.newChat({ initialMessage: prompt })
track('ai_assistant_dropdown_button_clicked', { source: 'log_explorer' })
}
// Click the button so the shortcut shows the same copied feedback
const handleCopyShortcut = () => copyButtonRef.current?.click()
return (
<div className="flex items-center gap-1">
{!selectedLogs.success && (
<span role="alert" className="text-xs text-destructive">
Selected logs contain invalid data.
</span>
)}
<Shortcut
id={SHORTCUT_IDS.RESULTS_COPY_JSON}
onTrigger={handleCopyShortcut}
options={{ enabled: hasValidSelection, registerInCommandMenu: true }}
side="bottom"
label="Copy selected logs as JSON"
>
<CopyButton
ref={copyButtonRef}
iconOnly
size="tiny"
variant="text"
className="px-1"
aria-label="Copy selected logs"
disabled={!hasValidSelection}
asyncText={() => formatLogsAsJson(selectedRows)}
/>
</Shortcut>
<ButtonTooltip
size="tiny"
variant="text"
className="px-1"
icon={<AiIconAnimation size={16} />}
aria-label="Explain with AI"
disabled={!hasValidSelection}
tooltip={{ content: { side: 'bottom', text: 'Explain with AI' } }}
onClick={handleOpenAiAssistant}
/>
</div>
)
}