Files
Joshen Lim 6965ce133c Ensure horizontal scrollbar is visible for unified logs raw json panel (#50661)
### Context

Just adds a `overflow-x` to ensure that the horizontal scrollbar is
visible for the unified logs raw json panel when viewing a single log as
per demo: (Can verify on staging/prod that it doesn't show up unless you
scroll all the way to the bottom for a very long raw json)


https://github.com/user-attachments/assets/e580fe2e-e7d9-4928-9335-fe957da0405a



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

## Summary by CodeRabbit

- **Style**
  - Improved horizontal visibility for content in the Raw JSON display.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-21 23:52:14 +08:00

277 lines
9.9 KiB
TypeScript

import { useParams } from 'common'
import { Check, Clock, Copy } from 'lucide-react'
import { useEffect, useRef, useState } from 'react'
import {
Button,
copyToClipboard,
ResizableHandle,
ResizablePanel,
Skeleton,
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from 'ui'
import { CodeBlock } from 'ui-patterns/CodeBlock'
import { PostgresFlowDetail } from './ServiceFlow/components/PostgresFlowDetail'
import {
MemoizedEdgeFunctionBlock,
MemoizedGoTrueBlock,
MemoizedNetworkBlock,
MemoizedPostgresBlock,
MemoizedPostgRESTBlock,
MemoizedStorageBlock,
} from './ServiceFlow/components/ServiceBlocks'
import { ServiceFlowPanelControls } from './ServiceFlow/components/ServiceFlowPanelControls'
import { DetailSectionHeader } from './ServiceFlow/components/shared/DetailSection'
import { ColumnSchema } from './UnifiedLogs.schema'
import { QuerySearchParamsType } from './UnifiedLogs.types'
import { getRawLogData, getRowTimestampMs } from './UnifiedLogs.utils'
import { useDataTable } from '@/components/ui/DataTable/providers/DataTableProvider'
import {
SERVICE_FLOW_TYPES,
ServiceFlowType,
useUnifiedLogInspectionQuery,
} from '@/data/logs/unified-log-inspection-query'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
interface ServiceFlowPanelProps {
dock: 'bottom' | 'right'
setDock: (value: 'bottom' | 'right') => void
selectedRow?: ColumnSchema
selectedRowKey: string
searchParameters: QuerySearchParamsType
}
export function getLogDataForMetadataVisibility(data: unknown, metadataVisible: boolean) {
if (metadataVisible || typeof data !== 'object' || data === null) return data
const redactedData = { ...data, metadata: undefined }
const rawLogData = 'raw_log_data' in data ? data.raw_log_data : undefined
if (typeof rawLogData !== 'object' || rawLogData === null) return redactedData
return {
...redactedData,
raw_log_data: { ...rawLogData, metadata: undefined },
}
}
export function ServiceFlowPanel({
dock,
setDock,
selectedRow,
selectedRowKey,
searchParameters,
}: ServiceFlowPanelProps) {
const { table, filterFields } = useDataTable()
const { ref: projectRef } = useParams()
const [activeTab, setActiveTab] = useState('overview')
const [jsonCopied, setJsonCopied] = useState(false)
const overviewScrollRef = useRef<HTMLDivElement>(null)
const jsonScrollRef = useRef<HTMLDivElement>(null)
useEffect(() => {
overviewScrollRef.current?.scrollTo({ top: 0 })
jsonScrollRef.current?.scrollTo({ top: 0 })
}, [selectedRowKey])
const logType = selectedRow?.log_type
const normalizedLogType = logType === 'edge function' ? 'edge-function' : logType
const serviceFlowType: ServiceFlowType | undefined = SERVICE_FLOW_TYPES.includes(
normalizedLogType as ServiceFlowType
)
? (normalizedLogType as ServiceFlowType)
: undefined
const shouldShowServiceFlow = !!serviceFlowType
useEffect(() => {
if (!shouldShowServiceFlow && activeTab === 'overview') {
setActiveTab('raw-json')
}
}, [shouldShowServiceFlow, activeTab])
const { logsMetadata } = useIsFeatureEnabled(['logs:metadata'])
const timestampMs = getRowTimestampMs(selectedRow)
// Query the logs API directly
const {
data: serviceFlowData,
isPending: isLoading,
error,
} = useUnifiedLogInspectionQuery(
{
projectRef: projectRef,
logId: selectedRow?.id,
type: serviceFlowType,
search: searchParameters,
logTimestampMs: timestampMs,
},
{ enabled: Boolean(selectedRow?.id) && Boolean(serviceFlowType) }
)
if (!selectedRowKey || !selectedRow) return null
const formattedTime = timestampMs ? new Date(timestampMs).toLocaleString() : null
// Prepare JSON data for Raw JSON tab
const jsonData =
shouldShowServiceFlow && serviceFlowData?.result?.[0] ? serviceFlowData.result[0] : selectedRow
const rawLogData = getRawLogData(jsonData)
const formattedJsonData = getLogDataForMetadataVisibility(rawLogData, 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">
<Tabs
defaultValue={shouldShowServiceFlow ? 'overview' : 'raw-json'}
value={activeTab}
onValueChange={setActiveTab}
className="flex h-full w-full flex-col"
>
<div className="flex items-center justify-between px-4 border-b border-border">
<TabsList className="flex h-auto gap-x-4 rounded-none border-none!">
{shouldShowServiceFlow && (
<TabsTrigger
value="overview"
className="border-b py-3 font-mono text-xs uppercase"
>
Overview
</TabsTrigger>
)}
<TabsTrigger value="raw-json" className="border-b py-3 font-mono text-xs uppercase">
Raw JSON
</TabsTrigger>
</TabsList>
<ServiceFlowPanelControls dock={dock} setDock={setDock} />
</div>
{shouldShowServiceFlow && (
<TabsContent
ref={overviewScrollRef}
value="overview"
className="mt-0 grow overflow-auto py-2"
>
{error ? (
<div className="py-8 text-center text-destructive">Error: {error.toString()}</div>
) : serviceFlowType === 'postgres' ? (
<PostgresFlowDetail
data={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
isLoading={isLoading}
filterFields={filterFields}
table={table}
/>
) : (
<div>
<DetailSectionHeader
title="Request started"
icon={Clock}
summary={formattedTime ?? undefined}
/>
<MemoizedNetworkBlock
data={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
isLoading={isLoading}
filterFields={filterFields}
table={table}
/>
{serviceFlowType === 'auth' ? (
<MemoizedGoTrueBlock
data={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
isLoading={isLoading}
filterFields={filterFields}
table={table}
/>
) : serviceFlowType === 'edge-function' ? (
<MemoizedEdgeFunctionBlock
data={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
isLoading={isLoading}
filterFields={filterFields}
table={table}
/>
) : serviceFlowType === 'storage' ? (
<MemoizedStorageBlock
data={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
isLoading={isLoading}
filterFields={filterFields}
table={table}
/>
) : (
<>
<MemoizedPostgRESTBlock
data={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
isLoading={isLoading}
filterFields={filterFields}
table={table}
/>
<MemoizedPostgresBlock
data={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
isLoading={isLoading}
filterFields={filterFields}
table={table}
/>
</>
)}
</div>
)}
</TabsContent>
)}
<TabsContent
ref={jsonScrollRef}
value="raw-json"
className="mt-0 grow overflow-auto bg-surface-100/50"
>
{isLoading && shouldShowServiceFlow && (
<div className="flex items-center gap-3 border-b border-border bg-surface-100 p-3 text-foreground-light">
<Skeleton className="h-4 w-4 animate-pulse rounded-full" />
<span className="text-sm">Enriching log...</span>
</div>
)}
<div className="sticky top-2 z-10 flex justify-end px-2 -mb-9 pointer-events-none">
<Button
size="tiny"
className="pointer-events-auto px-1.5"
icon={jsonCopied ? <Check size={12} /> : <Copy size={12} />}
onClick={() => {
copyToClipboard(JSON.stringify(formattedJsonData, null, 2))
setJsonCopied(true)
setTimeout(() => setJsonCopied(false), 1000)
}}
>
{jsonCopied ? 'Copied' : ''}
</Button>
</div>
<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(formattedJsonData, null, 2)}
</CodeBlock>
</TabsContent>
</Tabs>
</div>
</ResizablePanel>
</>
)
}