mirror of
https://github.com/supabase/supabase.git
synced 2026-10-16 06:39:09 +03:00
added DataGrid view for log browsing, added accompanying unit ui tests.
This commit is contained in:
1 parent
23f39b7a66
commit
f802436770
5 files changed
+145
-29
No files matched your search
@@ -21,8 +21,9 @@ const LogSelection = ({ log, onClose }: Props) => (
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="p-4 h-full">
|
||||
<Input.TextArea label="Metadata" className="font-mono" rows={25} disabled value={JSON.stringify(log.metadata, null, 2)} />
|
||||
<div className="p-4 h-full flex-col">
|
||||
<Typography.Text code className="block" small>{log.event_message} </Typography.Text>
|
||||
<Input.TextArea label="Metadata" className="font-mono mt-4" size="tiny" rows={25} disabled value={JSON.stringify(log.metadata, null, 2)} />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import { Typography, SidePanel } from '@supabase/ui'
|
||||
import { Typography } from '@supabase/ui'
|
||||
import dayjs from 'dayjs'
|
||||
import { Panel } from '@supabase/ui/dist/cjs/components/Tabs/Tabs'
|
||||
import LogSelection from './LogSelection'
|
||||
|
||||
import DataGrid from '@supabase/react-data-grid'
|
||||
interface Props {
|
||||
data: LogData[] | null | undefined
|
||||
}
|
||||
@@ -19,36 +17,62 @@ export interface LogData {
|
||||
metadata: Metadata
|
||||
}
|
||||
|
||||
interface Column {
|
||||
key: string
|
||||
name: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs table view with focus side panel
|
||||
*/
|
||||
const LogTable = ({ data }: Props) => {
|
||||
const columns = ["timestamp", "event_message"]
|
||||
|
||||
const [focusedLog, setFocusedLog] = useState<LogData | null>(null);
|
||||
const columns = ["timestamp", "event_message"].map(v => ({
|
||||
key: v, name: v,
|
||||
width: v === "timestamp" ? 240 : undefined,
|
||||
resizable: true,
|
||||
headerRenderer: () => {
|
||||
return <div className="flex items-center justify-center font-mono h-full">{v}</div>
|
||||
},
|
||||
formatter: ({ row }: any) => {
|
||||
let value = row[v]
|
||||
if (v === 'timestamp') {
|
||||
value = dayjs(Number(row["timestamp"]) / 1000).toString()
|
||||
}
|
||||
return <p className={`block whitespace-line-wrap ${row.id === focusedLog?.id ? "font-bold" : ""}`}>{value}</p>
|
||||
}
|
||||
}))
|
||||
|
||||
if (!data) {
|
||||
return null
|
||||
}
|
||||
const logMap = (data || []).reduce((acc: any, d) => {
|
||||
acc[d.id] = d
|
||||
return acc
|
||||
}, {})
|
||||
useEffect(() => {
|
||||
if (!data) return
|
||||
if (focusedLog && !(focusedLog.id in logMap)) {
|
||||
setFocusedLog(null)
|
||||
}
|
||||
}, [Object.keys(logMap)])
|
||||
|
||||
if (data === []) {
|
||||
return <Typography.Text className="text-center">No data returned from query</Typography.Text>
|
||||
}
|
||||
if (!data) return null
|
||||
|
||||
return (<section className="flex flex-row gap-x-4">
|
||||
<Table containerClassName="mx-auto container overflow-y h-full"
|
||||
head={columns.map((c) => <Table.th key={c}>{c}</Table.th>)}
|
||||
body={data.map(d =>
|
||||
<Table.tr key={d.id}>
|
||||
{columns.map(c => <Table.td key={c} className={`${d.id === focusedLog?.id ? "bg-green-800" : ""} `} onClick={() => setFocusedLog(d)}>
|
||||
{c === "timestamp" &&
|
||||
<Typography.Text className="block bg-green-800" small code>{dayjs(d[c] / 1000).toString()}</Typography.Text>}
|
||||
{c === "event_message" && <Typography.Text className="block" small code>{d[c]}</Typography.Text>}
|
||||
</Table.td>)}
|
||||
</Table.tr>)}
|
||||
/>
|
||||
<div className="flex-grow w-2/3">
|
||||
<DataGrid
|
||||
onSelectedCellChange={({ idx, rowIdx }) => setFocusedLog(data[rowIdx])}
|
||||
noRowsFallback={<Typography.Text className="text-center">No data returned from query</Typography.Text>}
|
||||
columns={columns as any}
|
||||
rowClass={r => `${r.id === focusedLog?.id ? "bg-green-800" : "cursor-pointer"}`}
|
||||
rows={data}
|
||||
rowKeyGetter={r => r.id}
|
||||
onRowClick={r => {
|
||||
const log = logMap[r.id]
|
||||
setFocusedLog(logMap[r.id])
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{focusedLog && (
|
||||
<div className="sticky top-0">
|
||||
<div className="sticky top-0 w-1/3">
|
||||
<LogSelection onClose={() => setFocusedLog(null)} log={focusedLog} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { SettingsLayout } from 'components/layouts'
|
||||
import { SettingsLayout } from 'components/layouts/'
|
||||
import { useRouter } from 'next/router'
|
||||
import LogPanel from 'components/ui/Logs/LogPanel'
|
||||
import { LOG_TYPE_LABEL_MAPPING } from 'lib/constants'
|
||||
@@ -14,7 +14,7 @@ import { Typography, Input } from '@supabase/ui'
|
||||
/**
|
||||
* Acts as a container component for the entire log display
|
||||
*/
|
||||
const LogPage = () => {
|
||||
export const LogPage = () => {
|
||||
const router = useRouter()
|
||||
const { ref, type } = router.query
|
||||
const title = LOG_TYPE_LABEL_MAPPING[type as string]
|
||||
@@ -28,7 +28,6 @@ const LogPage = () => {
|
||||
} = useSWR<{ data: LogData[], count: number, error: any }>(`${API_URL}/projects/${ref}/logs?type=${type}`, get, {
|
||||
revalidateOnFocus: false,
|
||||
})
|
||||
console.log(data)
|
||||
const { data: logData, error } = data || {}
|
||||
|
||||
const filteredData = logData && searchQuery ? logData.filter(d => {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import LogTable from 'components/ui/Logs/LogTable'
|
||||
import { render, fireEvent, waitFor, screen } from '@testing-library/react'
|
||||
test('can display log data', async () => {
|
||||
render(<LogTable data={[{
|
||||
id: "seome-uuid",
|
||||
timestamp: 1621323232312,
|
||||
event_message: "some event happened",
|
||||
metadata: {
|
||||
my_key: "something_value"
|
||||
}
|
||||
}]} />)
|
||||
|
||||
await waitFor(() => screen.getByText(/happened/))
|
||||
const row = screen.getByText(/happened/)
|
||||
fireEvent.click(row)
|
||||
await waitFor(() => screen.getByText(/my_key/))
|
||||
await waitFor(() => screen.getByText(/something_value/))
|
||||
})
|
||||
@@ -0,0 +1,74 @@
|
||||
// mock the fetch function
|
||||
jest.mock('lib/common/fetch')
|
||||
import { get } from 'lib/common/fetch'
|
||||
|
||||
// mock the settings layout
|
||||
jest.mock('components/layouts', () => ({
|
||||
SettingsLayout: jest.fn()
|
||||
.mockImplementation(({ children }) => <div>{children}</div>)
|
||||
}))
|
||||
|
||||
// mock mobx
|
||||
jest.mock('mobx-react-lite')
|
||||
import { observer } from 'mobx-react-lite'
|
||||
observer.mockImplementation(v => v)
|
||||
|
||||
// mock the router
|
||||
jest.mock('next/router')
|
||||
import { useRouter } from 'next/router'
|
||||
useRouter.mockReturnValue({ query: { ref: "123", type: "auth" } })
|
||||
|
||||
import { LogPage } from 'pages/project/[ref]/settings/logs/[type]'
|
||||
import { render, fireEvent, waitFor, screen } from '@testing-library/react'
|
||||
import { SWRConfig } from 'swr'
|
||||
|
||||
test('can display log data and metadata', async () => {
|
||||
const data = [{
|
||||
id: "seome-uuid",
|
||||
timestamp: 1621323232312,
|
||||
event_message: "some event happened",
|
||||
metadata: {
|
||||
my_key: "something_value"
|
||||
}
|
||||
}]
|
||||
get.mockResolvedValue({ data })
|
||||
render(<LogPage />)
|
||||
|
||||
await waitFor(() => screen.getByText(/happened/))
|
||||
const row = screen.getByText(/happened/)
|
||||
fireEvent.click(row)
|
||||
await waitFor(() => screen.getByText(/my_key/))
|
||||
await waitFor(() => screen.getByText(/something_value/))
|
||||
})
|
||||
|
||||
test('Refresh', async () => {
|
||||
const data = [{
|
||||
id: "some-uuid",
|
||||
timestamp: 1621323232312,
|
||||
event_message: "some event happened",
|
||||
metadata: {
|
||||
my_key: "something_value"
|
||||
}
|
||||
}]
|
||||
get.mockResolvedValue({ data })
|
||||
render(
|
||||
<SWRConfig value={{ dedupingInterval: null }}>
|
||||
<LogPage />
|
||||
</SWRConfig>
|
||||
)
|
||||
|
||||
const row = screen.getByText(/happened/)
|
||||
fireEvent.click(row)
|
||||
await waitFor(() => screen.getByText(/my_key/))
|
||||
expect(get).toBeCalledTimes(1)
|
||||
get.mockResolvedValue({ data: [] })
|
||||
|
||||
// simulate full refresh, loop because swr dedupe behaves weirdly
|
||||
await waitFor(() => {
|
||||
fireEvent.click(screen.getByText(/Refresh/))
|
||||
expect(get).toBeCalledTimes(2)
|
||||
})
|
||||
// when log line unmounts and it was focused, should close focus panel
|
||||
await waitFor(() => expect(() => screen.getByText(/my_key/)).toThrow())
|
||||
await waitFor(() => expect(() => screen.getByText(/happened/)).toThrow())
|
||||
})
|
||||
Reference in new issue
Block a user