mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Chore/unified logs fixes 02 (#36710)
* Temporarily disable cmd K shortcut for searching unified logs * Update placeholder for search input * Fix getLogsCountQuery for method * Standardize pathname parameter * Fix refresh button
This commit is contained in:
1 parent
3b3a75202e
commit
307f2843e1
7 files changed
+65
-43
No files matched your search
@@ -52,9 +52,10 @@ export const filterFields = [
|
||||
return (
|
||||
<div className="flex w-full items-center justify-between gap-2 font-mono">
|
||||
<span className="capitalize text-foreground/70 group-hover:text-accent-foreground text-xs">
|
||||
{props.label}
|
||||
{props.label.replace('_', ' ')}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground/70">{props.value}</span>
|
||||
{/* [Joshen] Temporarily hiding, this feels excessive */}
|
||||
{/* <span className="text-xs text-muted-foreground/70">{props.value}</span> */}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
||||
@@ -35,7 +35,11 @@ const buildQueryConditions = (search: QuerySearchParamsType) => {
|
||||
|
||||
// Handle scalar values
|
||||
if (value !== null && value !== undefined) {
|
||||
whereConditions.push(`${key} = '${value}'`)
|
||||
if (['host', 'pathname'].includes(key)) {
|
||||
whereConditions.push(`${key} LIKE '%${value}%'`)
|
||||
} else {
|
||||
whereConditions.push(`${key} = '${value}'`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -234,7 +238,7 @@ const getEdgeLogsQuery = () => {
|
||||
WHEN edge_logs_response.status_code >= 500 THEN 'error'
|
||||
ELSE 'success'
|
||||
END as level,
|
||||
edge_logs_request.path as path,
|
||||
edge_logs_request.path as pathname,
|
||||
edge_logs_request.host as host,
|
||||
null as event_message,
|
||||
edge_logs_request.method as method,
|
||||
@@ -273,7 +277,7 @@ const getPostgrestLogsQuery = () => {
|
||||
WHEN edge_logs_response.status_code >= 500 THEN 'error'
|
||||
ELSE 'success'
|
||||
END as level,
|
||||
edge_logs_request.path as path,
|
||||
edge_logs_request.path as pathname,
|
||||
edge_logs_request.host as host,
|
||||
null as event_message,
|
||||
edge_logs_request.method as method,
|
||||
@@ -311,7 +315,7 @@ const getPostgresLogsQuery = () => {
|
||||
WHEN pgl_parsed.error_severity = 'ERROR' THEN 'error'
|
||||
ELSE null
|
||||
END as level,
|
||||
null as path,
|
||||
null as pathname,
|
||||
null as host,
|
||||
event_message as event_message,
|
||||
null as method,
|
||||
@@ -341,7 +345,7 @@ const getEdgeFunctionLogsQuery = () => {
|
||||
WHEN fel_response.status_code >= 500 THEN 'error'
|
||||
ELSE 'success'
|
||||
END as level,
|
||||
fel_request.url as path,
|
||||
fel_request.url as pathname,
|
||||
fel_request.host as host,
|
||||
COALESCE(function_logs_agg.last_event_message, '') as event_message,
|
||||
fel_request.method as method,
|
||||
@@ -387,7 +391,7 @@ const getAuthLogsQuery = () => {
|
||||
WHEN el_in_al_response.status_code >= 500 THEN 'error'
|
||||
ELSE 'success'
|
||||
END as level,
|
||||
el_in_al_request.path as path,
|
||||
el_in_al_request.path as pathname,
|
||||
el_in_al_request.host as host,
|
||||
null as event_message,
|
||||
el_in_al_request.method as method,
|
||||
@@ -428,7 +432,7 @@ const getSupavisorLogsQuery = () => {
|
||||
WHEN LOWER(svl_metadata.level) = 'warn' OR LOWER(svl_metadata.level) = 'warning' THEN 'warning'
|
||||
ELSE 'success'
|
||||
END as level,
|
||||
null as path,
|
||||
null as pathname,
|
||||
null as host,
|
||||
null as event_message,
|
||||
null as method,
|
||||
@@ -478,7 +482,7 @@ SELECT
|
||||
log_type,
|
||||
status,
|
||||
level,
|
||||
path,
|
||||
pathname,
|
||||
host,
|
||||
event_message,
|
||||
method,
|
||||
@@ -498,8 +502,9 @@ ${finalWhere}
|
||||
* Also returns facets for all filter dimensions
|
||||
*/
|
||||
export const getLogsCountQuery = (search: QuerySearchParamsType): string => {
|
||||
// Use the buildQueryConditions helper
|
||||
const { finalWhere } = buildQueryConditions(search)
|
||||
const methodWhere =
|
||||
finalWhere.length > 0 ? `${finalWhere} AND method is NOT NULL` : 'WHERE method IS NOT NULL'
|
||||
|
||||
// Create a count query using the same unified logs CTE
|
||||
const sql = `
|
||||
@@ -530,8 +535,7 @@ UNION ALL
|
||||
-- Get counts by method
|
||||
SELECT 'method' as dimension, method as value, COUNT(*) as count
|
||||
FROM unified_logs
|
||||
${finalWhere}
|
||||
WHERE method IS NOT NULL
|
||||
${methodWhere}
|
||||
GROUP BY method
|
||||
`
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ import { FilterSideBar } from 'components/ui/DataTable/FilterSideBar'
|
||||
import { LiveButton } from 'components/ui/DataTable/LiveButton'
|
||||
import { LiveRow } from 'components/ui/DataTable/LiveRow'
|
||||
import { DataTableProvider } from 'components/ui/DataTable/providers/DataTableProvider'
|
||||
import { RefreshButton } from 'components/ui/DataTable/RefreshButton'
|
||||
import { TimelineChart } from 'components/ui/DataTable/TimelineChart'
|
||||
import { useUnifiedLogsChartQuery } from 'data/logs/unified-logs-chart-query'
|
||||
import { useUnifiedLogsCountQuery } from 'data/logs/unified-logs-count-query'
|
||||
@@ -45,6 +44,7 @@ import {
|
||||
TabsList_Shadcn_ as TabsList,
|
||||
TabsTrigger_Shadcn_ as TabsTrigger,
|
||||
} from 'ui'
|
||||
import { RefreshButton } from '../../ui/DataTable/RefreshButton'
|
||||
import { UNIFIED_LOGS_COLUMNS } from './components/Columns'
|
||||
import { MemoizedDataTableSheetContent } from './components/DataTableSheetContent'
|
||||
import { FunctionLogsTab } from './components/FunctionLogsTab'
|
||||
@@ -100,19 +100,36 @@ export const UnifiedLogs = () => {
|
||||
isLoading,
|
||||
isFetching,
|
||||
hasNextPage,
|
||||
refetch,
|
||||
refetch: refetchLogs,
|
||||
fetchNextPage,
|
||||
fetchPreviousPage,
|
||||
} = useUnifiedLogsInfiniteQuery({ projectRef, search: searchParameters })
|
||||
const { data: counts, isLoading: isLoadingCounts } = useUnifiedLogsCountQuery({
|
||||
const {
|
||||
data: counts,
|
||||
isLoading: isLoadingCounts,
|
||||
isFetching: isFetchingCounts,
|
||||
refetch: refetchCounts,
|
||||
} = useUnifiedLogsCountQuery({
|
||||
projectRef,
|
||||
search: searchParameters,
|
||||
})
|
||||
const { data: unifiedLogsChart = [] } = useUnifiedLogsChartQuery({
|
||||
const {
|
||||
data: unifiedLogsChart = [],
|
||||
isFetching: isFetchingCharts,
|
||||
refetch: refetchCharts,
|
||||
} = useUnifiedLogsChartQuery({
|
||||
projectRef,
|
||||
search: searchParameters,
|
||||
})
|
||||
|
||||
const refetchAllData = () => {
|
||||
refetchLogs()
|
||||
refetchCounts()
|
||||
refetchCharts()
|
||||
}
|
||||
|
||||
const isRefetchingData = isFetching || isFetchingCounts || isFetchingCharts
|
||||
|
||||
const rawFlatData = useMemo(() => {
|
||||
return unifiedLogsData?.pages?.flatMap((page) => page.data ?? []) ?? []
|
||||
}, [unifiedLogsData?.pages])
|
||||
@@ -275,10 +292,13 @@ export const UnifiedLogs = () => {
|
||||
<FilterSideBar />
|
||||
<div className="flex max-w-full flex-1 flex-col border-border sm:border-l overflow-hidden">
|
||||
<DataTableHeaderLayout setTopBarHeight={setTopBarHeight}>
|
||||
<DataTableFilterCommand searchParamsParser={SEARCH_PARAMS_PARSER} />
|
||||
<DataTableFilterCommand
|
||||
placeholder="Search logs..."
|
||||
searchParamsParser={SEARCH_PARAMS_PARSER}
|
||||
/>
|
||||
<DataTableToolbar
|
||||
renderActions={() => [
|
||||
<RefreshButton key="refresh" onClick={refetch} />,
|
||||
<RefreshButton isLoading={isRefetchingData} onRefresh={refetchAllData} />,
|
||||
fetchPreviousPage ? (
|
||||
<LiveButton
|
||||
key="live"
|
||||
|
||||
@@ -4,7 +4,6 @@ import { ParserBuilder } from 'nuqs'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
||||
import { useLocalStorage } from 'hooks/misc/useLocalStorage'
|
||||
import { useHotKey } from 'hooks/ui/useHotKey'
|
||||
import {
|
||||
cn,
|
||||
Command_Shadcn_ as Command,
|
||||
@@ -33,9 +32,13 @@ import {
|
||||
interface DataTableFilterCommandProps {
|
||||
// TODO: maybe use generics for the parser
|
||||
searchParamsParser: Record<string, ParserBuilder<any>>
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
export function DataTableFilterCommand({ searchParamsParser }: DataTableFilterCommandProps) {
|
||||
export function DataTableFilterCommand({
|
||||
searchParamsParser,
|
||||
placeholder = 'Search data table...',
|
||||
}: DataTableFilterCommandProps) {
|
||||
const { table, isLoading, filterFields: _filterFields, getFacetedUniqueValues } = useDataTable()
|
||||
const columnFilters = table.getState().columnFilters
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
@@ -59,7 +62,8 @@ export function DataTableFilterCommand({ searchParamsParser }: DataTableFilterCo
|
||||
|
||||
const trimmedInputValue = inputValue.trim()
|
||||
|
||||
useHotKey(() => setOpen((open) => !open), 'k')
|
||||
// [Joshen] Temporarily disabling as this conflicts with our current CMD K behaviour
|
||||
// useHotKey(() => setOpen((open) => !open), 'k')
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: we could check for ARRAY_DELIMITER or SLIDER_DELIMITER to auto-set filter when typing
|
||||
@@ -139,13 +143,15 @@ export function DataTableFilterCommand({ searchParamsParser }: DataTableFilterCo
|
||||
trimmedInputValue ? 'text-foreground' : 'text-foreground-light'
|
||||
)}
|
||||
>
|
||||
{trimmedInputValue ? inputValue : 'Search data table...'}
|
||||
{trimmedInputValue ? inputValue : placeholder}
|
||||
</span>
|
||||
<Kbd className="ml-auto text-muted-foreground group-hover:text-accent-foreground">
|
||||
{/* [Joshen] Temporarily disabling as this conflicts with existing CMD K shortcut */}
|
||||
{/* <Kbd className="ml-auto text-muted-foreground group-hover:text-accent-foreground">
|
||||
<span className="mr-1">⌘</span>
|
||||
<span>K</span>
|
||||
</Kbd>
|
||||
</Kbd> */}
|
||||
</button>
|
||||
|
||||
<Command
|
||||
className={cn(
|
||||
'overflow-visible rounded-lg border border-border shadow-md [&>div]:border-none bg',
|
||||
@@ -185,7 +191,7 @@ export function DataTableFilterCommand({ searchParamsParser }: DataTableFilterCo
|
||||
const word = getWordByCaretPosition({ value, caretPosition })
|
||||
setCurrentWord(word)
|
||||
}}
|
||||
placeholder="Search data table..."
|
||||
placeholder={placeholder}
|
||||
className="text-foreground"
|
||||
/>
|
||||
<div className="relative">
|
||||
|
||||
@@ -9,8 +9,6 @@ export function DataTableFilterResetButton<TData>({ value: _value }: DataTableFi
|
||||
const value = _value as string
|
||||
const column = table.getColumn(value)
|
||||
const filterValue = columnFilters.find((f) => f.id === value)?.value
|
||||
|
||||
// TODO: check if we could useMemo
|
||||
const filters = filterValue ? (Array.isArray(filterValue) ? filterValue : [filterValue]) : []
|
||||
|
||||
if (filters.length === 0) return null
|
||||
@@ -18,7 +16,8 @@ export function DataTableFilterResetButton<TData>({ value: _value }: DataTableFi
|
||||
return (
|
||||
<Button
|
||||
type="outline"
|
||||
className="h-5 rounded-full px-1.5 py-1 font-mono text-[10px]"
|
||||
icon={<X />}
|
||||
className="h-5 rounded-full px-1.5 py-1 font-mono text-[10px] [&>span]:-translate-y-[0.6px] space-x-1"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
column?.setFilterValue(undefined)
|
||||
@@ -29,13 +28,8 @@ export function DataTableFilterResetButton<TData>({ value: _value }: DataTableFi
|
||||
column?.setFilterValue(undefined)
|
||||
}
|
||||
}}
|
||||
icon={<X className="h-2.5 w-2.5 text-muted-foreground" />}
|
||||
asChild
|
||||
>
|
||||
{/* REMINDER: `AccordionTrigger` is also a button(!) and we get Hydration error when rendering button within button */}
|
||||
<div role="button" tabIndex={0}>
|
||||
<span>{filters.length}</span>
|
||||
</div>
|
||||
{filters.length}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -1,21 +1,18 @@
|
||||
import { LoaderCircle, RefreshCcw } from 'lucide-react'
|
||||
import { Button } from 'ui'
|
||||
|
||||
import { useDataTable } from './providers/DataTableProvider'
|
||||
|
||||
interface RefreshButtonProps {
|
||||
onClick: () => void
|
||||
isLoading: boolean
|
||||
onRefresh: () => void
|
||||
}
|
||||
|
||||
export function RefreshButton({ onClick }: RefreshButtonProps) {
|
||||
const { isLoading } = useDataTable()
|
||||
|
||||
export const RefreshButton = ({ isLoading, onRefresh }: RefreshButtonProps) => {
|
||||
return (
|
||||
<Button
|
||||
size="tiny"
|
||||
type="outline"
|
||||
disabled={isLoading}
|
||||
onClick={onClick}
|
||||
onClick={onRefresh}
|
||||
className="w-[26px]"
|
||||
icon={
|
||||
isLoading ? (
|
||||
|
||||
@@ -116,7 +116,7 @@ async function getUnifiedLogs(
|
||||
status: row.status || 200,
|
||||
method: row.method,
|
||||
host: row.host,
|
||||
pathname: (row.url || '').replace(/^https?:\/\/[^\/]+/, '') || row.path || '',
|
||||
pathname: (row.url || '').replace(/^https?:\/\/[^\/]+/, '') || row.pathname || '',
|
||||
event_message: row.event_message || row.body || '',
|
||||
headers:
|
||||
typeof row.headers === 'string' ? JSON.parse(row.headers || '{}') : row.headers || {},
|
||||
|
||||
Reference in new issue
Block a user