mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 18:35:07 +03:00
fixed ux issue, switched to use timestamp_end, added utc timestamp display
This commit is contained in:
1 parent
bdd1601e9f
commit
ce02f670c4
4 files changed
+58
-22
No files matched your search
@@ -13,6 +13,7 @@ import {
|
||||
} from '@supabase/ui'
|
||||
import { LogSearchCallback, LogTemplate } from '.'
|
||||
import dayjs from 'dayjs'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
interface Props {
|
||||
defaultSearchValue?: string
|
||||
defaultFromValue?: string
|
||||
@@ -26,6 +27,8 @@ interface Props {
|
||||
onSelectTemplate: (template: LogTemplate) => void
|
||||
}
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
/**
|
||||
* Logs control panel header + wrapper
|
||||
*/
|
||||
@@ -43,7 +46,7 @@ const LogPanel: FC<Props> = ({
|
||||
}) => {
|
||||
const [search, setSearch] = useState('')
|
||||
const [from, setFrom] = useState({ value: '', error: '' })
|
||||
const [defaultTimestamp, setDefaultTimestamp] = useState(dayjs().toISOString())
|
||||
const [defaultTimestamp, setDefaultTimestamp] = useState(dayjs().utc().toISOString())
|
||||
// sync local state with provided default value
|
||||
useEffect(() => {
|
||||
if (search !== defaultSearchValue) {
|
||||
@@ -52,7 +55,7 @@ const LogPanel: FC<Props> = ({
|
||||
}, [defaultSearchValue])
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultFromValue && from.value !== defaultFromValue) {
|
||||
if (from.value !== defaultFromValue) {
|
||||
setFrom({ value: defaultFromValue, error: '' })
|
||||
}
|
||||
}, [defaultFromValue])
|
||||
@@ -60,19 +63,21 @@ const LogPanel: FC<Props> = ({
|
||||
const handleFromChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value
|
||||
if (value !== '' && isNaN(Date.parse(value))) {
|
||||
setFrom({value, error: 'Invalid ISO 8601 timestamp' })
|
||||
setFrom({ value, error: 'Invalid ISO 8601 timestamp' })
|
||||
} else {
|
||||
setFrom({ value, error: '' })
|
||||
}
|
||||
}
|
||||
const handleFromReset = () => {
|
||||
const value = dayjs().toISOString()
|
||||
setFrom({ value, error: '' })
|
||||
const handleFromReset = async () => {
|
||||
setFrom({ value: '', error: '' })
|
||||
const value = dayjs().utc().toISOString()
|
||||
setDefaultTimestamp(value)
|
||||
onSearch({ query: search, from: '' })
|
||||
}
|
||||
|
||||
const handleSearch = () => onSearch({ query: search, from: from.value })
|
||||
|
||||
const showFromReset = from.value !== ''
|
||||
return (
|
||||
<div className="bg-panel-header-light dark:bg-panel-header-dark">
|
||||
<div className="px-2 py-1 flex items-center justify-between w-full">
|
||||
@@ -127,7 +132,7 @@ const LogPanel: FC<Props> = ({
|
||||
<div className="flex items-center gap-x-4">
|
||||
{!isCustomQuery && (
|
||||
<>
|
||||
<div className="flex flex-row gap-x-1">
|
||||
<div className="flex flex-row">
|
||||
<Dropdown
|
||||
side="bottom"
|
||||
align="end"
|
||||
@@ -139,6 +144,7 @@ const LogPanel: FC<Props> = ({
|
||||
value={from.value === '' ? defaultTimestamp : from.value}
|
||||
onChange={handleFromChange}
|
||||
error={from.error}
|
||||
className="w-72"
|
||||
actions={[
|
||||
from.value && (
|
||||
<IconX
|
||||
@@ -163,11 +169,26 @@ const LogPanel: FC<Props> = ({
|
||||
</Dropdown.Misc>
|
||||
}
|
||||
>
|
||||
<Button as="span" type="outline" icon={<IconClock size="tiny" />}>
|
||||
<Button
|
||||
as="span"
|
||||
size="tiny"
|
||||
className={showFromReset ? '!rounded-r-none' : ''}
|
||||
type={showFromReset ? 'outline' : 'text'}
|
||||
icon={<IconClock size="tiny" />}
|
||||
>
|
||||
{from.value ? 'Custom' : 'Now'}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<Button icon={<IconX />} title="Clear timestamp filter" />
|
||||
{showFromReset && (
|
||||
<Button
|
||||
size="tiny"
|
||||
className={showFromReset ? '!rounded-l-none' : ''}
|
||||
icon={<IconX size="tiny" />}
|
||||
type="outline"
|
||||
title="Clear timestamp filter"
|
||||
onClick={handleFromReset}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{/* wrap with form so that if user presses enter, the search value will submit automatically */}
|
||||
<form
|
||||
|
||||
@@ -33,6 +33,7 @@ import useSWRInfinite from 'swr/infinite'
|
||||
import { isUndefined } from 'lodash'
|
||||
import Flag from 'components/ui/Flag/Flag'
|
||||
import { useFlag } from 'hooks'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
/**
|
||||
* Acts as a container component for the entire log display
|
||||
@@ -49,7 +50,7 @@ export const LogPage: NextPage = () => {
|
||||
const logsQueryParamsSyncing = useFlag('logsQueryParamsSyncing')
|
||||
const logsCustomSql = useFlag('logsCustomSql')
|
||||
const router = useRouter()
|
||||
const { ref, type, q, s, ts } = router.query
|
||||
const { ref, type, q, s, te } = router.query
|
||||
const [editorId, setEditorId] = useState<string>(uuidv4())
|
||||
const [editorValue, setEditorValue] = useState('')
|
||||
const [mode, setMode] = useState<'simple' | 'custom'>('simple')
|
||||
@@ -82,9 +83,14 @@ export const LogPage: NextPage = () => {
|
||||
mode: 'simple',
|
||||
searchString: s as string,
|
||||
})
|
||||
} else {
|
||||
setParams({ ...params, search_query: '', where: '', sql: '' })
|
||||
}
|
||||
if (ts) {
|
||||
setParams({ ...params, timestamp_start: ts as string })
|
||||
if (te) {
|
||||
setParams({ ...params, timestamp_end: te as string })
|
||||
} else {
|
||||
setParams({ ...params, timestamp_end: '' })
|
||||
|
||||
}
|
||||
}, [logsQueryParamsSyncing])
|
||||
|
||||
@@ -148,7 +154,14 @@ export const LogPage: NextPage = () => {
|
||||
|
||||
const handleRefresh = () => {
|
||||
setLatestRefresh(new Date().toISOString())
|
||||
setParams({ ...params, timestamp_start: '' })
|
||||
setParams({ ...params, timestamp_end: '' })
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: {
|
||||
...router.query,
|
||||
te: undefined
|
||||
},
|
||||
})
|
||||
setSize(1)
|
||||
}
|
||||
|
||||
@@ -172,7 +185,7 @@ export const LogPage: NextPage = () => {
|
||||
where: isSelectQuery ? '' : template.searchString,
|
||||
sql: isSelectQuery ? template.searchString : '',
|
||||
search_query: '',
|
||||
timestamp_start: ''
|
||||
timestamp_end: ''
|
||||
}))
|
||||
setEditorId(uuidv4())
|
||||
}
|
||||
@@ -191,15 +204,16 @@ export const LogPage: NextPage = () => {
|
||||
...router.query,
|
||||
q: editorValue,
|
||||
s: undefined,
|
||||
ts: undefined
|
||||
te: undefined
|
||||
},
|
||||
})
|
||||
}
|
||||
const handleSearch: LogSearchCallback = ({ query, from }) => {
|
||||
const unixMicro = dayjs(from).valueOf() * 1000
|
||||
setParams((prev) => ({
|
||||
...prev,
|
||||
search_query: query || '',
|
||||
timestamp_start: from,
|
||||
timestamp_end: from ? String(unixMicro) : '' ,
|
||||
where: '',
|
||||
sql: '',
|
||||
}))
|
||||
@@ -210,7 +224,7 @@ export const LogPage: NextPage = () => {
|
||||
...router.query,
|
||||
q: undefined,
|
||||
s: query || '',
|
||||
ts: from
|
||||
te: unixMicro
|
||||
},
|
||||
})
|
||||
setEditorValue('')
|
||||
@@ -227,7 +241,7 @@ export const LogPage: NextPage = () => {
|
||||
onRefresh={handleRefresh}
|
||||
onSearch={handleSearch}
|
||||
defaultSearchValue={params.search_query}
|
||||
defaultFromValue={params.timestamp_start}
|
||||
defaultFromValue={params.timestamp_end ? dayjs(Number(params.timestamp_end)/1000).toISOString() : ''}
|
||||
onCustomClick={handleModeToggle}
|
||||
onSelectTemplate={onSelectTemplate}
|
||||
/>
|
||||
|
||||
@@ -101,7 +101,7 @@ test('timestamp from filter', async () => {
|
||||
userEvent.click(set)
|
||||
expect(mockFn).toBeCalled()
|
||||
await screen.findByText('Custom')
|
||||
await screen.findByTitle('Clear timestamp filter')
|
||||
await screen.findByTitle(/Clear timestamp filter/)
|
||||
})
|
||||
|
||||
test('custom query mode hides elements', async () => {
|
||||
|
||||
@@ -274,21 +274,22 @@ test('q= query param will populate the query input', async () => {
|
||||
expect(() => !screen.queryByDisplayValue(/someSearch/))
|
||||
})
|
||||
|
||||
test('ts= query param will populate the timestamp from input', async () => {
|
||||
test('te= query param will populate the timestamp from input', async () => {
|
||||
// get time 20 mins before
|
||||
const newDate = new Date()
|
||||
newDate.setMinutes(new Date().getMinutes() - 20)
|
||||
const isoString = newDate.toISOString()
|
||||
const unixMicro = newDate.getTime() * 1000 //microseconds
|
||||
|
||||
useRouter.mockReturnValueOnce({
|
||||
query: { ref: '123', type: 'api', ts: isoString },
|
||||
query: { ref: '123', type: 'api', te: unixMicro },
|
||||
push: jest.fn(),
|
||||
})
|
||||
render(<LogPage />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(get).toHaveBeenCalledWith(
|
||||
expect.stringContaining(`timestamp_start=${encodeURIComponent(isoString)}`)
|
||||
expect.stringContaining(`timestamp_end=${encodeURIComponent(unixMicro)}`)
|
||||
)
|
||||
})
|
||||
clickDropdown(await screen.findByText('Custom'))
|
||||
|
||||
Reference in new issue
Block a user