mirror of
https://github.com/supabase/supabase.git
synced 2026-10-19 16:19:10 +03:00
126 lines
3.8 KiB
TypeScript
126 lines
3.8 KiB
TypeScript
import dayjs from 'dayjs'
|
|
import { useEffect, useState, useMemo } from 'react'
|
|
import { Typography } from '@supabase/ui'
|
|
import DataGrid from '@supabase/react-data-grid'
|
|
|
|
import LogSelection from './LogSelection'
|
|
import { LogData } from './Logs.types'
|
|
import { isNil } from 'lodash'
|
|
|
|
interface Props {
|
|
isCustomQuery: boolean
|
|
data?: Array<LogData | Object>
|
|
}
|
|
type LogMap = { [id: string]: LogData }
|
|
|
|
/**
|
|
* Logs table view with focus side panel
|
|
*
|
|
* When in custom data display mode, the side panel will not open when focusing on logs.
|
|
*/
|
|
const LogTable = ({ isCustomQuery, data = [] }: Props) => {
|
|
const [focusedLog, setFocusedLog] = useState<LogData | null>(null)
|
|
const columnNames = Object.keys(data[0] || {})
|
|
// whether the data structure is LogData format.
|
|
const hasLogDataFormat =
|
|
columnNames.includes('timestamp') &&
|
|
columnNames.includes('event_message') &&
|
|
columnNames.length === 4
|
|
|
|
const columns = (hasLogDataFormat ? ['timestamp', 'event_message'] : columnNames).map((v) => ({
|
|
key: v,
|
|
name: v,
|
|
width: hasLogDataFormat && v === 'timestamp' ? 210 : undefined,
|
|
resizable: true,
|
|
headerRenderer: () => {
|
|
return <div className="flex items-center text-xs font-mono h-full">{v}</div>
|
|
},
|
|
formatter: ({ row }: any) => {
|
|
let value = row[v]
|
|
if (hasLogDataFormat && v === 'timestamp') {
|
|
value = dayjs(Number(row['timestamp']) / 1000).toISOString()
|
|
}
|
|
return (
|
|
<p
|
|
className={[
|
|
'block whitespace-wrap font-mono',
|
|
`${hasLogDataFormat && row.id === focusedLog?.id ? 'font-bold' : ''}`,
|
|
`${hasLogDataFormat && v === 'timestamp' ? 'text-green-900' : ''}`,
|
|
].join(' ')}
|
|
>
|
|
{value}
|
|
</p>
|
|
)
|
|
},
|
|
}))
|
|
|
|
const stringData = JSON.stringify(data)
|
|
const logMap = useMemo(() => {
|
|
if (!hasLogDataFormat) return {} as LogMap
|
|
const logData = data as LogData[]
|
|
return logData.reduce((acc: LogMap, d: LogData) => {
|
|
acc[d.id] = d
|
|
return acc
|
|
}, {}) as LogMap
|
|
}, [stringData])
|
|
|
|
useEffect(() => {
|
|
if (isNil(data)) return
|
|
if (focusedLog && !(focusedLog.id in logMap)) {
|
|
setFocusedLog(null)
|
|
}
|
|
}, [stringData])
|
|
|
|
if (!data) return null
|
|
|
|
// [Joshen] Hmm quite hacky now, but will do
|
|
const maxHeight = isCustomQuery ? 'calc(100vh - 42px - 10rem)' : 'calc(100vh - 42px - 3rem)'
|
|
|
|
const logDataRows = useMemo(() => {
|
|
if (!hasLogDataFormat) return data
|
|
return Object.values(logMap).sort((a, b) => b.timestamp - a.timestamp)
|
|
}, [stringData])
|
|
return (
|
|
<section className="flex flex-1 flex-row" style={{ maxHeight }}>
|
|
<DataGrid
|
|
style={{ height: '100%' }}
|
|
className="flex-grow flex-1"
|
|
onSelectedCellChange={({ idx, rowIdx }) => {
|
|
if (!hasLogDataFormat) return
|
|
setFocusedLog(data[rowIdx] as LogData)
|
|
}}
|
|
noRowsFallback={
|
|
<div className="p-4">
|
|
<Typography.Text type="secondary" small className="font-mono">
|
|
No data returned from query
|
|
</Typography.Text>
|
|
</div>
|
|
}
|
|
columns={columns as any}
|
|
rowClass={(r) => {
|
|
if (!hasLogDataFormat) return 'cursor-pointer'
|
|
const row = r as LogData
|
|
return `${row.id === focusedLog?.id ? 'bg-green-800' : 'cursor-pointer'}`
|
|
}}
|
|
rows={logDataRows}
|
|
rowKeyGetter={(r) => {
|
|
if (!hasLogDataFormat) return Object.keys(r)[0]
|
|
const row = r as LogData
|
|
return row.id
|
|
}}
|
|
onRowClick={(r) => {
|
|
if (!hasLogDataFormat) return
|
|
const row = r as LogData
|
|
setFocusedLog(logMap[row.id])
|
|
}}
|
|
/>
|
|
{focusedLog && (
|
|
<div className="w-2/5 flex flex-col">
|
|
<LogSelection onClose={() => setFocusedLog(null)} log={focusedLog} />
|
|
</div>
|
|
)}
|
|
</section>
|
|
)
|
|
}
|
|
export default LogTable
|