cleaned up code

This commit is contained in:
TzeYiing authored and Copple committed 2022-03-30 17:07:39 +02:00
1 parent 5db0514624
commit 3ca2322e35
12 files changed
+175 -346

No files matched your search

@@ -11,7 +11,6 @@ import {
IconSearch,
IconClock,
Popover,
IconPlay,
} from '@supabase/ui'
import { LogSearchCallback, LogTemplate } from '.'
import dayjs from 'dayjs'
@@ -30,8 +29,6 @@ interface Props {
onSelectTemplate: (template: LogTemplate) => void
isShowingEventChart: boolean
onToggleEventChart: () => void
editorControls: React.ReactNode
}
dayjs.extend(utc)
@@ -53,8 +50,6 @@ const LogPanel: FC<Props> = ({
onSelectTemplate,
isShowingEventChart,
onToggleEventChart,
editorControls,
}) => {
const [search, setSearch] = useState('')
const [to, setTo] = useState({ value: '', error: '' })
@@ -266,7 +261,6 @@ const LogPanel: FC<Props> = ({
</>
)}
</div>
{editorControls}
</div>
</div>
)
@@ -11,7 +11,7 @@ import DatabasePostgresSelectionRender from './LogSelectionRenderers/DatabasePos
interface Props {
log: LogData
onClose: () => void
queryType: QueryType
queryType?: QueryType
}
/**
@@ -38,11 +38,9 @@ const LogSelection: FC<Props> = ({ log, onClose, queryType }) => {
switch (queryType) {
case 'api':
return DatabaseApiSelectionHeaderRender(log)
break
default:
return null
break
}
}
@@ -1,11 +1,10 @@
import dayjs from 'dayjs'
import { useEffect, useState, useMemo } from 'react'
import { Badge, Button, IconDownload, IconDownloadCloud, IconEye, Typography } from '@supabase/ui'
import DataGrid, { Row } from '@supabase/react-data-grid'
import { Button, IconDownloadCloud, IconEye, Typography } from '@supabase/ui'
import DataGrid from '@supabase/react-data-grid'
import LogSelection from './LogSelection'
import { LogData, Mode, QueryType } from './Logs.types'
import { isNil } from 'lodash'
import { LogData, QueryType } from './Logs.types'
import { SeverityFormatter, ResponseCodeFormatter, HeaderFormmater } from './LogsFormatters'
// column renders
@@ -15,7 +14,7 @@ import DatabasePostgresColumnRender from './LogColumnRenderers/DatabasePostgresC
interface Props {
isCustomQuery: boolean
data?: Array<LogData | Object>
queryType: QueryType
queryType?: QueryType
}
type LogMap = { [id: string]: LogData }
@@ -24,193 +23,130 @@ type LogMap = { [id: string]: LogData }
*
* When in custom data display mode, the side panel will not open when focusing on logs.
*/
const LogTable = ({ isCustomQuery, data = [], queryType }: Props) => {
const LogTable = ({ data = [], queryType }: Props) => {
const [focusedLog, setFocusedLog] = useState<LogData | null>(null)
const columnNames = Object.keys(data[0] || {})
const hasId = columnNames.includes('id')
const hasTimestamp = columnNames.includes('timestamp')
// console.log('queryType', queryType)
// console.log('focusedLog', focusedLog)
isCustomQuery = false
// whether the data structure is LogData format.
// const hasLogDataFormat =
// columnNames.includes('timestamp') &&
// columnNames.includes('event_message') &&
// columnNames.length === 4
const hasLogDataFormat = !isCustomQuery
const DEFAULT_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 DEFAULT_COLUMNS = columnNames.map((v) => ({ key: v, name: v, resizable: true }))
let columns
// console.log('QUERY TYPE', queryType)
switch (queryType) {
case 'api':
if (isCustomQuery) {
columns = DEFAULT_COLUMNS
if (!queryType) {
columns = DEFAULT_COLUMNS
} else {
switch (queryType) {
case 'api':
columns = DatabaseApiColumnRender
break
}
// console.log('is api, running column')
columns = DatabaseApiColumnRender
break
case 'database':
if (isCustomQuery) {
columns = DEFAULT_COLUMNS
case 'database':
columns = DatabasePostgresColumnRender
break
}
// console.log('is api, running column')
columns = DatabasePostgresColumnRender
break
case 'fn_edge':
if (isCustomQuery) {
columns = DEFAULT_COLUMNS
break
}
columns = [
{
key: 'timestamp',
headerRenderer: () => (
<div className="flex w-full justify-end h-full">
<HeaderFormmater value={'timestamp'} />
</div>
),
name: 'timestamp',
formatter: (data: any) => (
<span className="flex w-full h-full items-center gap-1">
<span className="text-xs">{dayjs(data?.row?.timestamp / 1000).format('DD MMM')}</span>
<span className="text-xs">
{dayjs(data?.row?.timestamp / 1000).format('HH:mm:ss')}
case 'fn_edge':
columns = [
{
key: 'timestamp',
headerRenderer: () => (
<div className="flex w-full justify-end h-full">
<HeaderFormmater value={'timestamp'} />
</div>
),
name: 'timestamp',
formatter: (data: any) => (
<span className="flex w-full h-full items-center gap-1">
<span className="text-xs">
{dayjs(data?.row?.timestamp / 1000).format('DD MMM')}
</span>
<span className="text-xs">
{dayjs(data?.row?.timestamp / 1000).format('HH:mm:ss')}
</span>
</span>
{/* {data?.row?.timestamp} */}
</span>
),
width: 128,
},
{
key: 'status_code',
headerRenderer: () => <HeaderFormmater value={'Status'} />,
name: 'status_code',
formatter: (data: any) => (
<ResponseCodeFormatter row={data} value={data.row.status_code} />
),
),
width: 128,
},
{
key: 'status_code',
headerRenderer: () => <HeaderFormmater value={'Status'} />,
name: 'status_code',
formatter: (data: any) => (
<ResponseCodeFormatter row={data} value={data.row.status_code} />
),
width: 0,
resizable: true,
},
{
key: 'method',
headerRenderer: () => <HeaderFormmater value={'method'} />,
width: 0,
resizable: true,
},
{
key: 'id',
headerRenderer: () => <HeaderFormmater value={'id'} />,
name: 'id',
resizable: true,
},
]
break
case 'functions':
columns = [
{
key: 'timestamp',
headerRenderer: () => (
<div className="flex w-full justify-end h-full">
<HeaderFormmater value={'timestamp'} />
</div>
),
name: 'timestamp',
formatter: (data: any) => (
<span className="flex w-full h-full items-center gap-1">
<span className="text-xs !text-scale-1100">
{dayjs(data?.row?.timestamp / 1000).format('DD MMM')}
</span>
<span className="text-xs !text-scale-1100">
{dayjs(data?.row?.timestamp / 1000).format('HH:mm:ss')}
</span>
</span>
),
width: 128,
resizable: true,
},
{
key: 'level',
headerRenderer: () => <HeaderFormmater value={'Level'} />,
name: 'level',
formatter: (data: any) => <SeverityFormatter value={data.row.level} />,
width: 24,
resizable: true,
},
{
key: 'event_message',
headerRenderer: () => <HeaderFormmater value={'Event message'} />,
resizable: true,
},
]
break
width: 0,
resizable: true,
},
{
key: 'method',
headerRenderer: () => <HeaderFormmater value={'method'} />,
width: 0,
resizable: true,
},
{
key: 'id',
headerRenderer: () => <HeaderFormmater value={'id'} />,
name: 'id',
resizable: true,
},
]
break
case 'functions':
if (isCustomQuery) {
default:
columns = DEFAULT_COLUMNS
break
}
columns = [
{
key: 'timestamp',
headerRenderer: () => (
<div className="flex w-full justify-end h-full">
<HeaderFormmater value={'timestamp'} />
</div>
),
name: 'timestamp',
formatter: (data: any) => (
<span className="flex w-full h-full items-center gap-1">
<span className="text-xs !text-scale-1100">
{dayjs(data?.row?.timestamp / 1000).format('DD MMM')}
</span>
<span className="text-xs !text-scale-1100">
{dayjs(data?.row?.timestamp / 1000).format('HH:mm:ss')}
</span>
{/* {data?.row?.timestamp} */}
</span>
),
width: 128,
resizable: true,
},
{
key: 'level',
headerRenderer: () => <HeaderFormmater value={'Level'} />,
name: 'level',
formatter: (data: any) => <SeverityFormatter value={data.row.level} />,
width: 24,
resizable: true,
},
{
key: 'event_message',
headerRenderer: () => <HeaderFormmater value={'Event message'} />,
resizable: true,
},
]
break
default:
columns = DEFAULT_COLUMNS
break
}
}
const stringData = JSON.stringify(data)
const logMap = useMemo(() => {
if (!hasLogDataFormat) return {} as LogMap
if (!hasId) return {} as LogMap
const logData = data as LogData[]
return logData.reduce((acc: LogMap, d: LogData) => {
acc[d.id] = d
return acc
}, {}) as LogMap
}, [JSON.stringify(data)])
}, [stringData])
const stringData = JSON.stringify(data)
useEffect(() => {
if (!hasLogDataFormat) return
if (isNil(data)) return
if (!hasId || data === null) return
if (focusedLog && !(focusedLog.id in logMap)) {
setFocusedLog(null)
}
@@ -218,20 +154,21 @@ const LogTable = ({ isCustomQuery, data = [], queryType }: Props) => {
if (!data) return null
// console.log(data)
// console.log('logMap', logMap)
// console.log(columns)
// [Joshen] Hmm quite hacky now, but will do
const maxHeight = isCustomQuery ? 'calc(100vh - 42px - 10rem)' : 'calc(100vh - 42px - 3rem)'
const maxHeight = !queryType ? '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)
if (hasId && hasTimestamp) {
return Object.values(logMap).sort((a, b) => b.timestamp - a.timestamp)
} else {
const deduped = [...new Set(data)]
return deduped
}
}, [stringData])
return (
<>
{isCustomQuery && (
{!queryType && (
<div
className="
w-full bg-scale-300 rounded
@@ -264,12 +201,12 @@ const LogTable = ({ isCustomQuery, data = [], queryType }: Props) => {
style={{ height: '100%' }}
className={`
flex-grow flex-1
${isCustomQuery ? '' : ' data-grid--simple-logs'}
${false ? '' : ' data-grid--simple-logs'}
`}
rowHeight={40}
headerRowHeight={0}
onSelectedCellChange={({ idx, rowIdx }) => {
if (!hasLogDataFormat) return
if (!hasId) return
setFocusedLog(data[rowIdx] as LogData)
}}
noRowsFallback={
@@ -281,22 +218,9 @@ const LogTable = ({ isCustomQuery, data = [], queryType }: Props) => {
}
columns={columns as any}
rowClass={(r) => {
// if (!hasLogDataFormat) return 'cursor-pointer'
const row = r as LogData
let classes = []
//@ts-ignore
// if (row.status_code) {
// const response = parseResponseCode(row.status_code)
// if (response == 'warning') {
// classes.push('!bg-yellow-200 !dark:bg-yellow-100')
// }
// if (response == 'error') {
// classes.push('!bg-red-200')
// }
// }
classes.push(
`${row.id === focusedLog?.id ? '!bg-scale-400 rdg-row--focussed' : 'cursor-pointer'}`
)
@@ -305,7 +229,7 @@ const LogTable = ({ isCustomQuery, data = [], queryType }: Props) => {
}}
rows={logDataRows}
rowKeyGetter={(r) => {
if (!hasLogDataFormat) return Object.keys(r)[0]
if (!hasId) return Object.keys(r)[0]
const row = r as LogData
return row.id
}}
@@ -315,16 +239,15 @@ const LogTable = ({ isCustomQuery, data = [], queryType }: Props) => {
setFocusedLog(logMap[row.id])
}}
/>
{/* {hasLogDataFormat ||
(focusedLog && ( */}
<div className="w-1/2 flex flex-col">
<LogSelection
onClose={() => setFocusedLog(null)}
log={focusedLog}
queryType={queryType}
/>
</div>
{/* ))} */}
{hasId && focusedLog && (
<div className="w-1/2 flex flex-col">
<LogSelection
onClose={() => setFocusedLog(null)}
log={focusedLog}
queryType={queryType}
/>
</div>
)}
</section>
</>
)
@@ -209,7 +209,23 @@ export const genQueryParams = (params: { [k: string]: string }) => {
return qs
}
export const FILTER_OPTIONS: any = {
interface Filter {
label: string
key: string
options: {
key: string
label: string
description?: string
}[]
}
type FilterOptions = {
[table: string] : {
[filterName: string]: Filter
}
}
export const FILTER_OPTIONS: FilterOptions = {
// Postgres logs
postgres_logs: {
severity: {
@@ -1,19 +1,12 @@
import { Severity } from '@sentry/browser'
import { filter } from 'lodash'
import { LogsTableName, SQL_FILTER_TEMPLATES } from './Logs.constants'
import { FilterObject } from './Logs.types'
export function filterSqlWhereBuilder(
filters: FilterObject,
filters: FilterObject | any,
table: LogsTableName,
searchQuery: string
) {
// console.log('raw filters', filters)
// remove any filter arrays that are empty
const filtersSanitized: any = Object.values(filters).filter((x) => x && x.length > 0)
// console.log('filtersSanitized', filtersSanitized)
const filtersSanitized: any = Object.values(filters).filter((x: any) => x && x.length > 0)
const keys = Object.keys(filters)
@@ -25,17 +18,13 @@ export function filterSqlWhereBuilder(
keys.map((x: string, i) => {
// do not parse empty key
if (!x) {
return
}
if (!x) return
let count = 0
const filterKeyArray: string[] = []
filters[x].map((value: string, i: number) => {
// first line should be WHERE
const last = i === filters[x].length - 1
if (count === 0) {
@@ -55,11 +44,8 @@ export function filterSqlWhereBuilder(
}
})
// console.log('filterKeyArray', filterKeyArray)
whereArray.push(...filterKeyArray)
// console.log('filters length', filtersSanitized.length)
// if there are multiple filters in an 'and' sequence
// then `and` is inserted between them
if (filtersSanitized.length > 0 && i < filtersSanitized.length - 1) {
@@ -68,21 +54,12 @@ export function filterSqlWhereBuilder(
return filterKeyArray
})
// console.log('whereArray', whereArray)
// console.log('sqlWhereArray', sqlWhereArray)
return whereArray
}
export function filterReducer(state: FilterObject, action: any) {
// console.log(state, action)
const oldState = { ...state }
const newState = { ...oldState, ...action }
console.log('newState', newState)
return newState
}
@@ -21,7 +21,8 @@ export interface LogData {
id: string
timestamp: number
event_message: string
metadata: Metadata
metadata: Metadata,
[other: string]: unknown;
}
export interface LogTemplate {
@@ -2,33 +2,25 @@ import React, { FC, useEffect, useState } from 'react'
import {
Button,
Input,
Dropdown,
Typography,
IconChevronDown,
IconRefreshCw,
IconX,
Toggle,
IconSearch,
IconClock,
Popover,
IconLink,
IconExternalLink,
IconCalendar,
IconEye,
Checkbox,
Form,
} from '@supabase/ui'
import { LogSearchCallback, LogTemplate } from '.'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import { FILTER_OPTIONS, LogsTableName } from './Logs.constants'
import { LogsFilter } from './Logs.filter'
import { DatePicker } from 'components/ui/DatePicker'
import DatePickers from './Logs.DatePickers'
interface Props {
defaultSearchValue?: string
defaultToValue?: string
defaultFromValue?: string
templates?: any
isLoading: boolean
isCustomQuery: boolean
@@ -42,10 +34,6 @@ interface Props {
dispatchWhereFilters: (x: any) => void
whereFilters: any
table: LogsTableName
te: { value: string; error: string }
ts: { value: string; error: string }
setTo: ({}) => void
setFrom: ({}) => void
}
dayjs.extend(utc)
@@ -69,10 +57,6 @@ const PreviewFilterPanel: FC<Props> = ({
dispatchWhereFilters,
whereFilters: filters,
table,
// te: to,
// ts: from,
// setTo,
// setFrom,
}) => {
const [search, setSearch] = useState('')
@@ -189,8 +173,7 @@ const PreviewFilterPanel: FC<Props> = ({
onSubmit={(e) => {
// prevent redirection
e.preventDefault()
setSearch(localSearchValue)
handleSearch(localSearchValue)
handleSearch()
}}
>
<Input
@@ -200,7 +183,7 @@ const PreviewFilterPanel: FC<Props> = ({
onChange={(e) => setlocalSearchValue(e.target.value)}
onBlur={(e: React.FocusEvent<HTMLInputElement>) => {
setSearch(e.target.value)
handleSearch(localSearchValue)
handleSearch()
}}
icon={
<div className="text-scale-900">
@@ -211,7 +194,7 @@ const PreviewFilterPanel: FC<Props> = ({
actions={
hasEdits && (
<button
onClick={() => handleSearch(localSearchValue)}
onClick={() => handleSearch()}
className="text-scale-1100 hover:text-scale-1200 mx-2"
>
{'↲'}
@@ -234,17 +217,14 @@ const PreviewFilterPanel: FC<Props> = ({
</div>
<div className="flex items-center gap-2">
{Object.values(FILTER_OPTIONS[table]).map((x) => {
// console.log('filter option', x)
return (
<LogsFilter
key={`${x.key}-filter`}
options={x}
dispatchFilters={dispatchWhereFilters}
filtersState={filters}
/>
)
})}
{Object.values(FILTER_OPTIONS[table]).map((x) => (
<LogsFilter
key={`${x.key}-filter`}
options={x}
dispatchFilters={dispatchWhereFilters}
filtersState={filters}
/>
))}
</div>
<div className="flex flex-row">
@@ -1,4 +1,3 @@
import useSWR from 'swr'
import React, { useEffect, useReducer, useState } from 'react'
import { NextPage } from 'next'
import { useRouter } from 'next/router'
@@ -9,38 +8,24 @@ import {
IconAlertCircle,
IconRewind,
Button,
IconInfo,
Card,
Input,
} from '@supabase/ui'
import { withAuth } from 'hooks'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { SettingsLayout } from 'components/layouts/'
import CodeEditor from 'components/ui/CodeEditor'
import {
LogTable,
LogEventChart,
Count,
Logs,
LogTemplate,
TEMPLATES,
LogData,
LogSearchCallback,
LOG_TYPE_LABEL_MAPPING,
genDefaultQuery,
genCountQuery,
LogsTableName,
filterSqlWhereBuilder,
FilterObject,
filterReducer,
QueryType,
} from 'components/interfaces/Settings/Logs'
import { uuidv4 } from 'lib/helpers'
import useSWRInfinite, { SWRInfiniteKeyLoader } from 'swr/infinite'
import { isUndefined } from 'lodash'
import dayjs from 'dayjs'
import InformationBox from 'components/ui/InformationBox'
import useLogsPreview from 'hooks/analytics/useLogsPreview'
import PreviewFilterPanel from 'components/interfaces/Settings/Logs/PreviewFilterPanel'
@@ -125,7 +110,7 @@ export const LogPage: NextPage = () => {
}
const handleSearch: LogSearchCallback = ({ query, to, from, fromMicro, toMicro }) => {
let toValue, fromValue
let toValue
if (to || toMicro) {
toValue = toMicro ? toMicro : dayjs(to).valueOf() * 1000
@@ -196,10 +181,6 @@ export const LogPage: NextPage = () => {
dispatchWhereFilters={dispatchWhereFilters}
whereFilters={whereFilters}
table={table}
te={te}
ts={ts}
setTo={setTo}
setFrom={setFrom}
/>
{/* {showChart && (
<div>
@@ -223,7 +204,7 @@ export const LogPage: NextPage = () => {
</div>
)}
<LogTable data={logData} isCustomQuery={false} queryType={type} />
<LogTable data={logData} isCustomQuery={false} queryType={type as QueryType} />
<div className="p-2">
<Button onClick={() => loadOlder()} icon={<IconRewind />} type="default">
Load older
@@ -1,45 +1,15 @@
import useSWR from 'swr'
import React, { useEffect, useState } from 'react'
import { NextPage } from 'next'
import { useRouter } from 'next/router'
import { observer } from 'mobx-react-lite'
import {
Typography,
IconLoader,
IconAlertCircle,
IconRewind,
Button,
IconInfo,
Card,
Input,
} from '@supabase/ui'
import { Typography, IconLoader, IconAlertCircle, Button, Card, Input } from '@supabase/ui'
import { withAuth } from 'hooks'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { SettingsLayout } from 'components/layouts/'
import CodeEditor from 'components/ui/CodeEditor'
import {
LogPanel,
LogTable,
LogEventChart,
Count,
Logs,
LogTemplate,
TEMPLATES,
LogData,
LogSearchCallback,
LOG_TYPE_LABEL_MAPPING,
genDefaultQuery,
genCountQuery,
LogsTableName,
} from 'components/interfaces/Settings/Logs'
import { LogPanel, LogTable, LogTemplate, TEMPLATES } from 'components/interfaces/Settings/Logs'
import { uuidv4 } from 'lib/helpers'
import useSWRInfinite, { SWRInfiniteKeyLoader } from 'swr/infinite'
import { isUndefined } from 'lodash'
import dayjs from 'dayjs'
import InformationBox from 'components/ui/InformationBox'
import useLogsPreview from 'hooks/analytics/useLogsPreview'
import useLogsQuery from 'hooks/analytics/useLogsQuery'
/**
+3 -3
View File
@@ -32,20 +32,20 @@ test('dedupes log lines with exact id', async () => {
id: 'some-uuid',
timestamp: 1621323232312,
event_message: 'some event happened',
metadata: {}
metadata: {},
},
{
id: 'some-uuid',
timestamp: 1621323232312,
event_message: 'some event happened',
metadata: {}
metadata: {},
},
]}
/>
)
// should only have one element, this line will fail if there are >1 element
await waitFor(() => screen.getByText(/happened/))
await screen.findByText(/happened/)
})
test('can display custom columns and headers based on data input', async () => {
@@ -72,7 +72,7 @@ beforeEach(() => {
useRouter.mockReset()
useRouter.mockReturnValue(defaultRouterMock())
})
test('can display log data and metadata', async () => {
test('can display log data', async () => {
get.mockResolvedValue({
result: [
logDataFixture({
+5 -16
View File
@@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -17,16 +13,9 @@
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": "."
"baseUrl": ".",
"downlevelIteration": true
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"**/*.js",
"**/*.jsx"
],
"exclude": [
"node_modules"
]
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
"exclude": ["node_modules"]
}