mirror of
https://github.com/supabase/supabase.git
synced 2026-07-01 00:34:18 +08:00
Remove unified logs related dead code (#46459)
## Context Just removing unified logs related dead code (Not used, not imported) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Streamlined the Service Flow view by removing legacy timeline, collapsible sections, and some detailed step UI for a cleaner visualization. * Simplified the Unified Logs surface by reducing exposed types, consolidating query logic, and removing an internal event bus. * Removed legacy list/detail and sheet UI pieces to tighten the logs interface and public API surface. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46459?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -3,12 +3,12 @@ import { Cable, ChevronDown, Clock, Database } from 'lucide-react'
|
||||
import { memo } from 'react'
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from 'ui'
|
||||
|
||||
import { ColumnSchema } from '../../../UnifiedLogs.schema'
|
||||
import { getRowTimestampMs } from '../../../UnifiedLogs.utils'
|
||||
import { postgresDetailsFields, postgresPrimaryFields } from '../../config/serviceFlowFields'
|
||||
import { BlockFieldConfig } from '../../types'
|
||||
import { DetailRow } from '../shared/DetailRow'
|
||||
import { DetailSectionHeader } from '../shared/DetailSection'
|
||||
import { ColumnSchema } from '../../UnifiedLogs.schema'
|
||||
import { getRowTimestampMs } from '../../UnifiedLogs.utils'
|
||||
import { postgresDetailsFields, postgresPrimaryFields } from '../config/serviceFlowFields'
|
||||
import { BlockFieldConfig } from '../types'
|
||||
import { DetailRow } from './shared/DetailRow'
|
||||
import { DetailSectionHeader } from './shared/DetailSection'
|
||||
import { DataTableFilterField } from '@/components/ui/DataTable/DataTable.types'
|
||||
|
||||
interface PostgresFlowDetailProps {
|
||||
@@ -1,38 +0,0 @@
|
||||
import { Clock } from 'lucide-react'
|
||||
import { memo } from 'react'
|
||||
|
||||
import { ColumnSchema } from '../../../UnifiedLogs.schema'
|
||||
import { StyledIcon } from '../shared/TimelineStep'
|
||||
|
||||
// Request Started - Simple header component with connecting line
|
||||
export const MemoizedRequestStartedBlock = memo(function RequestStartedBlock({
|
||||
data,
|
||||
}: {
|
||||
data: ColumnSchema
|
||||
}) {
|
||||
// Convert microseconds to milliseconds for JavaScript Date
|
||||
const timestampMs = data?.timestamp
|
||||
? data.timestamp / 1000
|
||||
: data?.date
|
||||
? data.date.getTime()
|
||||
: null
|
||||
const formattedTime = timestampMs ? new Date(timestampMs).toLocaleString() : null
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between py-0 px-2">
|
||||
<div className="flex items-center gap-2 text-sm text-foreground-light">
|
||||
<StyledIcon icon={Clock} title="Request started" />
|
||||
<span>Request started</span>
|
||||
</div>
|
||||
{formattedTime && (
|
||||
<span className="text-sm font-mono text-foreground-light">{formattedTime}</span>
|
||||
)}
|
||||
</div>
|
||||
{/* Connecting line to first timeline block */}
|
||||
<div className="border-l h-4 ml-5"></div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
MemoizedRequestStartedBlock.displayName = 'MemoizedRequestStartedBlock'
|
||||
@@ -1,103 +0,0 @@
|
||||
import { Clock } from 'lucide-react'
|
||||
import { memo } from 'react'
|
||||
|
||||
import { ColumnSchema } from '../../../UnifiedLogs.schema'
|
||||
import { EventMessage } from '../shared/EventMessage'
|
||||
import { StyledIcon } from '../shared/TimelineStep'
|
||||
|
||||
interface ResponseCompletedBlockProps {
|
||||
data: ColumnSchema
|
||||
enrichedData?: Record<string, any>
|
||||
}
|
||||
|
||||
// Response (final step) - Shows completion details for HTTP or database operations
|
||||
export const MemoizedResponseCompletedBlock = memo(function ResponseCompletedBlock({
|
||||
data,
|
||||
enrichedData,
|
||||
}: ResponseCompletedBlockProps) {
|
||||
// Check if this is a postgres log
|
||||
const isPostgresLog = (enrichedData?.log_type || data?.log_type) === 'postgres'
|
||||
|
||||
// HTTP response handling
|
||||
const hasError = data?.status && Number(data.status) >= 400
|
||||
const responseTime = enrichedData?.response_time_ms || enrichedData?.duration_ms
|
||||
const status = Number(data?.status)
|
||||
|
||||
// Postgres operation handling
|
||||
const eventMessage = enrichedData?.event_message || data?.event_message
|
||||
const severity = enrichedData?.error_severity
|
||||
const hasPostgresError = severity && ['error', 'fatal'].includes(severity.toLowerCase())
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between py-0 px-2">
|
||||
<div className="flex items-center gap-2 text-sm text-foreground-light">
|
||||
<StyledIcon icon={Clock} title="Response" />
|
||||
<span>{isPostgresLog ? 'Operation Result' : 'Response'}</span>
|
||||
</div>
|
||||
|
||||
{/* Status display */}
|
||||
{isPostgresLog
|
||||
? // Postgres status
|
||||
severity && (
|
||||
<span
|
||||
className={`text-sm font-mono ${
|
||||
hasPostgresError ? 'text-destructive' : 'text-foreground-light'
|
||||
}`}
|
||||
>
|
||||
{hasPostgresError ? `${severity.toUpperCase()} Error` : `${severity.toUpperCase()}`}
|
||||
</span>
|
||||
)
|
||||
: // HTTP status
|
||||
data?.status && (
|
||||
<span className="text-sm font-mono text-foreground-light">
|
||||
{hasError ? `${data.status} Error` : `${data.status} Success`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Completion message */}
|
||||
{isPostgresLog ? (
|
||||
// Postgres completion message
|
||||
<div className="text-xs text-foreground-light px-2 py-1">
|
||||
{hasPostgresError
|
||||
? 'Database operation completed with error'
|
||||
: 'Database operation completed successfully'}
|
||||
</div>
|
||||
) : (
|
||||
// HTTP completion message
|
||||
responseTime && (
|
||||
<div className="text-xs text-foreground-light px-2 py-1">
|
||||
{hasError
|
||||
? `Error response sent to client in ${responseTime}ms`
|
||||
: `Response sent to client in ${responseTime}ms`}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{/* Error/Event details */}
|
||||
{isPostgresLog
|
||||
? // Show postgres event message
|
||||
eventMessage && (
|
||||
<div className="px-2">
|
||||
<EventMessage message={eventMessage} severity={severity} />
|
||||
</div>
|
||||
)
|
||||
: // Show HTTP error details
|
||||
hasError && (
|
||||
<div className="px-2 py-1 mt-1">
|
||||
<div className="text-xs font-medium text-destructive mb-1">Error Details</div>
|
||||
<div className="text-xs text-foreground-light">
|
||||
{status >= 500
|
||||
? 'Server error occurred during request processing'
|
||||
: status >= 400
|
||||
? 'Client error - check request parameters and authentication'
|
||||
: 'Request completed with error status'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
MemoizedResponseCompletedBlock.displayName = 'MemoizedResponseCompletedBlock'
|
||||
@@ -7,14 +7,14 @@ import { BlockFieldConfig, BlockFieldProps, ServiceFlowBlockProps } from '../../
|
||||
import { DetailRow } from './DetailRow'
|
||||
import { DetailSectionHeader } from './DetailSection'
|
||||
|
||||
export interface BlockSection {
|
||||
interface BlockSection {
|
||||
title: string
|
||||
icon?: LucideIcon
|
||||
fields: BlockFieldConfig[]
|
||||
collapsible?: boolean
|
||||
}
|
||||
|
||||
export interface FieldWithSeeMoreSection {
|
||||
interface FieldWithSeeMoreSection {
|
||||
type: 'fieldWithSeeMore'
|
||||
primaryField: BlockFieldConfig
|
||||
additionalFields: BlockFieldConfig[]
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import { Table } from '@tanstack/react-table'
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Button, Collapsible, CollapsibleContent, CollapsibleTrigger } from 'ui'
|
||||
|
||||
import { BlockFieldConfig } from '../../types'
|
||||
import { BlockField } from './BlockField'
|
||||
import { DataTableFilterField } from '@/components/ui/DataTable/DataTable.types'
|
||||
|
||||
interface CollapsibleSectionProps {
|
||||
title: string
|
||||
fields: BlockFieldConfig[]
|
||||
data: any
|
||||
enrichedData?: any
|
||||
isLoading?: boolean
|
||||
filterFields: DataTableFilterField<any>[]
|
||||
table: Table<any>
|
||||
defaultOpen?: boolean
|
||||
}
|
||||
|
||||
export const CollapsibleSection = ({
|
||||
title,
|
||||
fields,
|
||||
data,
|
||||
enrichedData,
|
||||
isLoading,
|
||||
filterFields,
|
||||
table,
|
||||
defaultOpen = false,
|
||||
}: CollapsibleSectionProps) => {
|
||||
const [isOpen, setIsOpen] = useState(defaultOpen)
|
||||
|
||||
return (
|
||||
<div className="border-t border-border">
|
||||
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
||||
<CollapsibleTrigger asChild>
|
||||
<Button
|
||||
type="text"
|
||||
size="tiny"
|
||||
className="w-full justify-start py-1 px-2 h-auto text-xs font-medium text-foreground-light hover:text-foreground"
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
{isOpen ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
|
||||
<span>{title}</span>
|
||||
</div>
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="transition-all data-closed:animate-collapsible-up data-open:animate-collapsible-down pt-1">
|
||||
{fields.map((field) => (
|
||||
<BlockField
|
||||
key={field.id}
|
||||
config={field}
|
||||
data={data}
|
||||
enrichedData={enrichedData}
|
||||
isLoading={isLoading}
|
||||
filterFields={filterFields}
|
||||
table={table}
|
||||
/>
|
||||
))}
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import { Database, LucideIcon } from 'lucide-react'
|
||||
import { Badge } from 'ui'
|
||||
|
||||
import { LOG_TYPES } from '../../../UnifiedLogs.constants'
|
||||
import { formatServiceTypeForDisplay } from '../../../UnifiedLogs.utils'
|
||||
import { LEVELS } from '@/components/ui/DataTable/DataTable.constants'
|
||||
|
||||
type LogType = (typeof LOG_TYPES)[number]
|
||||
type Level = (typeof LEVELS)[number]
|
||||
|
||||
export interface EventMessageProps {
|
||||
message: string
|
||||
severity?: Level
|
||||
icon?: LucideIcon
|
||||
serviceType?: LogType
|
||||
}
|
||||
|
||||
export const EventMessage = ({
|
||||
message,
|
||||
severity,
|
||||
icon: Icon = Database,
|
||||
serviceType = 'postgres',
|
||||
}: EventMessageProps) => {
|
||||
const getSeverityIcon = (severity?: Level) => {
|
||||
switch (severity) {
|
||||
case 'error':
|
||||
return 'text-destructive'
|
||||
case 'warning':
|
||||
return 'text-warning'
|
||||
default:
|
||||
return 'text-foreground-light'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-3 border-t border-border pt-3">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Icon size={14} className={getSeverityIcon(severity)} />
|
||||
<span className="text-xs font-medium text-foreground-light">
|
||||
{formatServiceTypeForDisplay(serviceType)} Event
|
||||
{severity && (
|
||||
<Badge
|
||||
variant={
|
||||
severity === 'error'
|
||||
? 'destructive'
|
||||
: severity === 'warning'
|
||||
? 'warning'
|
||||
: 'default'
|
||||
}
|
||||
className="ml-2"
|
||||
>
|
||||
{severity}
|
||||
</Badge>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg border border-border bg-surface-100">
|
||||
<div className="text-xs font-mono break-all whitespace-pre-wrap leading-relaxed max-h-32 overflow-y-auto">
|
||||
{message}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import { Auth, EdgeFunctions, Storage } from 'icons'
|
||||
import { Clock, Database, Globe, LucideIcon, Server } from 'lucide-react'
|
||||
|
||||
import { getStatusLevel } from '../../../UnifiedLogs.utils'
|
||||
import { DataTableColumnStatusCode } from '@/components/ui/DataTable/DataTableColumn/DataTableColumnStatusCode'
|
||||
|
||||
// Type for icon components (covers both lucide-react and our icon library)
|
||||
type IconComponent = LucideIcon | React.ComponentType<any>
|
||||
|
||||
// Reusable styled icon component
|
||||
export const StyledIcon = ({
|
||||
icon: Icon,
|
||||
title: _title,
|
||||
}: {
|
||||
icon: IconComponent
|
||||
title: string
|
||||
}) => (
|
||||
<div className="flex items-center gap-2 bg-surface-300 rounded-sm p-0.5 border justify-center border-foreground-muted">
|
||||
<Icon className="w-4 h-4 text-foreground-lighter" strokeWidth={1} />
|
||||
</div>
|
||||
)
|
||||
|
||||
export const TimelineStep = ({
|
||||
title,
|
||||
status,
|
||||
statusText,
|
||||
children,
|
||||
isLast = false,
|
||||
}: {
|
||||
title: string
|
||||
status?: number | string
|
||||
statusText?: string
|
||||
children: React.ReactNode
|
||||
isLast?: boolean
|
||||
}) => (
|
||||
<>
|
||||
<div className="relative">
|
||||
{/* Timeline dot - positioned on the left timeline line */}
|
||||
<div className="py-1 bg-surface-100/50 border-r border-t border-l rounded-t px-2">
|
||||
<div>
|
||||
<div className="flex flex-row justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
{title === 'Request started' && <StyledIcon icon={Clock} title={title} />}
|
||||
{title === 'Network' && <StyledIcon icon={Globe} title={title} />}
|
||||
{title === 'Data API' && <StyledIcon icon={Server} title={title} />}
|
||||
{title === 'Authentication' && <StyledIcon icon={Auth} title={title} />}
|
||||
{title === 'Edge Function' && <StyledIcon icon={EdgeFunctions} title={title} />}
|
||||
{title === 'Storage' && <StyledIcon icon={Storage} title={title} />}
|
||||
{title === 'Postgres' && <StyledIcon icon={Database} title={title} />}
|
||||
{title === 'Response' && <StyledIcon icon={Clock} title={title} />}
|
||||
<h3 className="text-sm text-foreground tracking-wide">{title}</h3>
|
||||
</div>
|
||||
|
||||
{statusText && (
|
||||
<span className="text-xs text-foreground-light tracking-wide">{statusText}</span>
|
||||
)}
|
||||
|
||||
{status && (
|
||||
<DataTableColumnStatusCode
|
||||
value={status}
|
||||
level={getStatusLevel(status)}
|
||||
className="text-xs"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main section box */}
|
||||
<div className="border rounded-b border-border">
|
||||
{/* Content */}
|
||||
<dl className="space-y-0 divide-y px-1 py-1 bg-surface-100/50">{children}</dl>
|
||||
</div>
|
||||
{!isLast && <div className="border-l h-3 ml-5"></div>}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
@@ -24,23 +24,6 @@ const formatStorageDate = (dateString: string): string => {
|
||||
// NETWORK FIELDS
|
||||
// =============================================================================
|
||||
|
||||
// Field configurations - using filterable field IDs where possible
|
||||
export const originFields: BlockFieldConfig[] = [
|
||||
{
|
||||
id: 'date', // Matches filterFields 'date' (timerange) - FILTERABLE
|
||||
label: 'Time',
|
||||
getValue: (data) => {
|
||||
if (!data?.timestamp && !data?.date) return null
|
||||
try {
|
||||
const timestamp = data?.timestamp || data?.date
|
||||
return new Date(timestamp).toLocaleString()
|
||||
} catch {
|
||||
return 'Invalid date'
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
// Primary Network Fields (Always Visible) - FILTERABLE
|
||||
export const networkPrimaryFields: BlockFieldConfig[] = [
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from 'ui'
|
||||
import { CodeBlock } from 'ui-patterns/CodeBlock'
|
||||
|
||||
import { PostgresFlowDetail } from './ServiceFlow/components/blocks/PostgresFlowDetail'
|
||||
import { PostgresFlowDetail } from './ServiceFlow/components/PostgresFlowDetail'
|
||||
import {
|
||||
MemoizedEdgeFunctionBlock,
|
||||
MemoizedGoTrueBlock,
|
||||
|
||||
@@ -1,34 +1,11 @@
|
||||
import { User } from 'lucide-react'
|
||||
import { cn } from 'ui'
|
||||
|
||||
import { LOG_TYPES, METHODS, STATUS_CODE_LABELS } from './UnifiedLogs.constants'
|
||||
import { ColumnSchema } from './UnifiedLogs.schema'
|
||||
import { LogsMeta, SheetField } from './UnifiedLogs.types'
|
||||
import { getLevelLabel } from './UnifiedLogs.utils'
|
||||
import { LEVELS } from '@/components/ui/DataTable/DataTable.constants'
|
||||
import { DataTableFilterField, Option } from '@/components/ui/DataTable/DataTable.types'
|
||||
import { getLevelColor } from '@/components/ui/DataTable/DataTable.utils'
|
||||
import { useFormatDateTime } from '@/lib/datetime'
|
||||
|
||||
const DateCell = (props: { date: ColumnSchema['date'] }) => {
|
||||
const formatDateTime = useFormatDateTime()
|
||||
const month = formatDateTime(props.date, 'MMM')
|
||||
const day = formatDateTime(props.date, 'DD')
|
||||
const year = formatDateTime(props.date, 'YYYY')
|
||||
const time = formatDateTime(props.date, 'HH:mm:ss')
|
||||
|
||||
return (
|
||||
<div className="font-mono whitespace-nowrap flex items-center gap-1 justify-end">
|
||||
<span>{month}</span>
|
||||
<span className="text-foreground/50">·</span>
|
||||
<span>{day}</span>
|
||||
<span className="text-foreground/50">·</span>
|
||||
<span>{year}</span>
|
||||
<span className="text-foreground/50">·</span>
|
||||
<span>{time}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// instead of filterFields, maybe just 'fields' with a filterDisabled prop?
|
||||
// that way, we could have 'message' or 'headers' field with label and value as well as type!
|
||||
@@ -152,38 +129,3 @@ export const filterFields = [
|
||||
},
|
||||
},
|
||||
] satisfies DataTableFilterField<ColumnSchema>[]
|
||||
|
||||
export const sheetFields = [
|
||||
{
|
||||
id: 'id',
|
||||
label: 'Request ID',
|
||||
type: 'readonly',
|
||||
skeletonClassName: 'w-64',
|
||||
},
|
||||
{
|
||||
id: 'date',
|
||||
label: 'Date',
|
||||
type: 'timerange',
|
||||
component: DateCell,
|
||||
skeletonClassName: 'w-36',
|
||||
},
|
||||
{
|
||||
id: 'auth_user',
|
||||
label: 'Auth User',
|
||||
type: 'readonly',
|
||||
condition: (props) => Boolean(props.auth_user),
|
||||
component: (props) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<User size={14} className="text-foreground-lighter" />
|
||||
<span className="font-mono">{props.auth_user}</span>
|
||||
</div>
|
||||
),
|
||||
skeletonClassName: 'w-56',
|
||||
},
|
||||
{
|
||||
id: 'pathname',
|
||||
label: 'Pathname',
|
||||
type: 'input',
|
||||
skeletonClassName: 'w-56',
|
||||
},
|
||||
] satisfies SheetField<ColumnSchema, LogsMeta>[]
|
||||
|
||||
@@ -445,99 +445,6 @@ ${cteName} AS (
|
||||
`
|
||||
}
|
||||
|
||||
export const getUnifiedLogsCountCTE = (): SafeLogSqlFragment => safeSql`
|
||||
WITH unified_logs AS (
|
||||
-- Single scan of edge_logs covering edge gateway, postgrest, and storage
|
||||
select
|
||||
id,
|
||||
CASE
|
||||
WHEN edge_logs_request.path LIKE '%/rest/%' THEN 'postgrest'
|
||||
WHEN edge_logs_request.path LIKE '%/storage/%' THEN 'storage'
|
||||
ELSE 'edge'
|
||||
END as log_type,
|
||||
CAST(edge_logs_response.status_code AS STRING) as status,
|
||||
CASE
|
||||
WHEN edge_logs_response.status_code BETWEEN 200 AND 299 THEN 'success'
|
||||
WHEN edge_logs_response.status_code BETWEEN 400 AND 499 THEN 'warning'
|
||||
WHEN edge_logs_response.status_code >= 500 THEN 'error'
|
||||
ELSE 'success'
|
||||
END as level,
|
||||
edge_logs_request.path as pathname,
|
||||
edge_logs_request.method as method
|
||||
from edge_logs as el
|
||||
cross join unnest(metadata) as edge_logs_metadata
|
||||
cross join unnest(edge_logs_metadata.request) as edge_logs_request
|
||||
cross join unnest(edge_logs_metadata.response) as edge_logs_response
|
||||
|
||||
union all
|
||||
|
||||
-- Postgres logs
|
||||
select
|
||||
id,
|
||||
'postgres' as log_type,
|
||||
CAST(pgl_parsed.sql_state_code AS STRING) as status,
|
||||
CASE
|
||||
WHEN pgl_parsed.error_severity = 'LOG' THEN 'success'
|
||||
WHEN pgl_parsed.error_severity = 'WARNING' THEN 'warning'
|
||||
WHEN pgl_parsed.error_severity = 'FATAL' THEN 'error'
|
||||
WHEN pgl_parsed.error_severity = 'ERROR' THEN 'error'
|
||||
ELSE null
|
||||
END as level,
|
||||
null as pathname,
|
||||
null as method
|
||||
from postgres_logs as pgl
|
||||
cross join unnest(pgl.metadata) as pgl_metadata
|
||||
cross join unnest(pgl_metadata.parsed) as pgl_parsed
|
||||
|
||||
union all
|
||||
|
||||
-- Edge function logs
|
||||
select
|
||||
fel.id,
|
||||
'edge function' as log_type,
|
||||
CAST(fel_response.status_code AS STRING) as status,
|
||||
CASE
|
||||
WHEN fel_response.status_code BETWEEN 200 AND 299 THEN 'success'
|
||||
WHEN fel_response.status_code BETWEEN 400 AND 499 THEN 'warning'
|
||||
WHEN fel_response.status_code >= 500 THEN 'error'
|
||||
ELSE 'success'
|
||||
END as level,
|
||||
fel_request.pathname as pathname,
|
||||
fel_request.method as method
|
||||
from function_edge_logs as fel
|
||||
cross join unnest(metadata) as fel_metadata
|
||||
cross join unnest(fel_metadata.response) as fel_response
|
||||
cross join unnest(fel_metadata.request) as fel_request
|
||||
|
||||
union all
|
||||
|
||||
-- Auth logs
|
||||
select
|
||||
el_in_al.id as id,
|
||||
'auth' as log_type,
|
||||
CAST(el_in_al_response.status_code AS STRING) as status,
|
||||
CASE
|
||||
WHEN el_in_al_response.status_code BETWEEN 200 AND 299 THEN 'success'
|
||||
WHEN el_in_al_response.status_code BETWEEN 400 AND 499 THEN 'warning'
|
||||
WHEN el_in_al_response.status_code >= 500 THEN 'error'
|
||||
ELSE 'success'
|
||||
END as level,
|
||||
el_in_al_request.path as pathname,
|
||||
el_in_al_request.method as method
|
||||
from auth_logs as al
|
||||
cross join unnest(metadata) as al_metadata
|
||||
left join (
|
||||
edge_logs as el_in_al
|
||||
cross join unnest(metadata) as el_in_al_metadata
|
||||
cross join unnest(el_in_al_metadata.response) as el_in_al_response
|
||||
cross join unnest(el_in_al_response.headers) as el_in_al_response_headers
|
||||
cross join unnest(el_in_al_metadata.request) as el_in_al_request
|
||||
)
|
||||
on al_metadata.request_id = el_in_al_response_headers.cf_ray
|
||||
WHERE al_metadata.request_id is not null
|
||||
)
|
||||
`
|
||||
|
||||
export const getLogsCountQuery = (search: QuerySearchParamsType): SafeLogSqlFragment => {
|
||||
const effectiveLogTypes = getEffectiveLogTypes(search)
|
||||
const logTypeConditions = buildConditions(search, 'log_type')
|
||||
|
||||
@@ -1,25 +1,10 @@
|
||||
import type { inferParserType } from 'nuqs'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
import { LOG_TYPES, SEARCH_PARAMS_PARSER } from './UnifiedLogs.constants'
|
||||
import { SEARCH_PARAMS_PARSER } from './UnifiedLogs.constants'
|
||||
|
||||
type Percentile = 50 | 75 | 90 | 95 | 99
|
||||
|
||||
export type LogType = (typeof LOG_TYPES)[number]
|
||||
|
||||
export type UnifiedLogSchema = {
|
||||
id: string
|
||||
timestamp: Date
|
||||
log_type: LogType
|
||||
code: string
|
||||
level: string
|
||||
path: string | null
|
||||
event_message: string
|
||||
method: string
|
||||
api_role: string
|
||||
auth_user: string | null
|
||||
}
|
||||
|
||||
export type LogsMeta = {
|
||||
currentPercentiles: Record<Percentile, number>
|
||||
}
|
||||
@@ -29,10 +14,6 @@ export type PageParam = { cursor: number; direction: 'next' | 'prev' } | undefin
|
||||
export type SearchParamsType = inferParserType<typeof SEARCH_PARAMS_PARSER>
|
||||
export type QuerySearchParamsType = Omit<SearchParamsType, 'uuid' | 'live'>
|
||||
|
||||
export type SearchParams = {
|
||||
[key: string]: string | string[] | undefined
|
||||
}
|
||||
|
||||
/** ----------------------------------------- */
|
||||
|
||||
export type SheetField<TData, TMeta = Record<string, unknown>> = {
|
||||
|
||||
@@ -4,22 +4,6 @@ import { cn } from 'ui'
|
||||
import { FacetMetadataSchema } from './UnifiedLogs.schema'
|
||||
import { LEVELS } from '@/components/ui/DataTable/DataTable.constants'
|
||||
|
||||
export const logEventBus = {
|
||||
listeners: new Map<string, Set<(rowId: string) => void>>(),
|
||||
|
||||
on(event: 'selectTraceTab', callback: (rowId: string) => void) {
|
||||
if (!this.listeners.has(event)) {
|
||||
this.listeners.set(event, new Set())
|
||||
}
|
||||
this.listeners.get(event)?.add(callback)
|
||||
return () => this.listeners.get(event)?.delete(callback)
|
||||
},
|
||||
|
||||
emit(event: 'selectTraceTab', rowId: string) {
|
||||
this.listeners.get(event)?.forEach((callback) => callback(rowId))
|
||||
},
|
||||
}
|
||||
|
||||
export const getFacetedUniqueValues = <TData>(facets?: Record<string, FacetMetadataSchema>) => {
|
||||
return (_table: TTable<TData>, columnId: string) => {
|
||||
return new Map(facets?.[columnId]?.rows?.map(({ value, total }) => [value, total]) || [])
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
import { Table } from '@tanstack/react-table'
|
||||
import { HTMLAttributes, memo } from 'react'
|
||||
import { cn, Skeleton } from 'ui'
|
||||
|
||||
import { SheetField } from '../UnifiedLogs.types'
|
||||
import { DataTableFilterField } from '@/components/ui/DataTable/DataTable.types'
|
||||
import { DataTableSheetRowAction } from '@/components/ui/DataTable/DataTableSheetRowAction'
|
||||
|
||||
interface SheetDetailsContentSkeletonProps<TData, TMeta> {
|
||||
fields: SheetField<TData, TMeta>[]
|
||||
}
|
||||
|
||||
const SheetDetailsContentSkeleton = <TData, TMeta>({
|
||||
fields,
|
||||
}: SheetDetailsContentSkeletonProps<TData, TMeta>) => {
|
||||
return (
|
||||
<dl className="divide-y">
|
||||
{fields.map((field) => (
|
||||
<div
|
||||
key={field.id.toString()}
|
||||
className="flex gap-4 py-2 text-sm justify-between items-center"
|
||||
>
|
||||
<dt className="shrink-0 text-muted-foreground">{field.label}</dt>
|
||||
<div>
|
||||
<Skeleton className={cn('h-5 w-52', field.skeletonClassName)} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
)
|
||||
}
|
||||
|
||||
interface DataTableSheetContentProps<TData, TMeta> extends HTMLAttributes<HTMLDListElement> {
|
||||
data?: TData
|
||||
table: Table<TData>
|
||||
fields: SheetField<TData, TMeta>[]
|
||||
filterFields: DataTableFilterField<TData>[]
|
||||
metadata?: TMeta & {
|
||||
totalRows: number
|
||||
filterRows: number
|
||||
totalRowsFetched: number
|
||||
}
|
||||
}
|
||||
|
||||
export function DataTableSheetContent<TData, TMeta>({
|
||||
data,
|
||||
table,
|
||||
className,
|
||||
fields,
|
||||
filterFields,
|
||||
metadata,
|
||||
...props
|
||||
}: DataTableSheetContentProps<TData, TMeta>) {
|
||||
if (!data) return <SheetDetailsContentSkeleton fields={fields} />
|
||||
|
||||
return (
|
||||
<dl className={cn('divide-y', className)} {...props}>
|
||||
{fields.map((field) => {
|
||||
if (field.condition && !field.condition(data)) return null
|
||||
|
||||
const Component = field.component
|
||||
const value = String(data[field.id])
|
||||
|
||||
return (
|
||||
<div key={field.id.toString()}>
|
||||
{field.type === 'readonly' ? (
|
||||
<div
|
||||
className={cn(
|
||||
'flex gap-4 my-1 py-1 text-sm justify-between items-center w-full',
|
||||
field.className
|
||||
)}
|
||||
>
|
||||
<dt className="shrink-0 text-muted-foreground">{field.label}</dt>
|
||||
<dd className="font-mono w-full text-right truncate">
|
||||
{Component ? <Component {...data} metadata={metadata} /> : value}
|
||||
</dd>
|
||||
</div>
|
||||
) : (
|
||||
<DataTableSheetRowAction
|
||||
fieldValue={field.id}
|
||||
filterFields={filterFields}
|
||||
value={value}
|
||||
table={table}
|
||||
className={cn(
|
||||
'flex gap-4 my-1 py-1 text-sm justify-between items-center w-full',
|
||||
field.className
|
||||
)}
|
||||
label={field.label}
|
||||
>
|
||||
<dt className="shrink-0 text-muted-foreground">{field.label}</dt>
|
||||
<dd className="font-mono w-full text-right truncate">
|
||||
{Component ? <Component {...data} metadata={metadata} /> : value}
|
||||
</dd>
|
||||
</DataTableSheetRowAction>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</dl>
|
||||
)
|
||||
}
|
||||
|
||||
export const MemoizedDataTableSheetContent = memo(DataTableSheetContent, (prev, next) => {
|
||||
// REMINDER: only check if data is the same, rest is useless
|
||||
return prev.data === next.data
|
||||
}) as typeof DataTableSheetContent
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Auth, EdgeFunctions, Storage } from 'icons'
|
||||
import { Box, Code2, Database } from 'lucide-react'
|
||||
import { cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
|
||||
import { type LOG_TYPES } from '../UnifiedLogs.constants'
|
||||
|
||||
@@ -50,17 +50,3 @@ export const LogTypeIcon = ({
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export const LogTypeIconWithText = ({
|
||||
type,
|
||||
size = 16,
|
||||
strokeWidth = 1.5,
|
||||
className,
|
||||
}: LogTypeIconProps) => {
|
||||
return (
|
||||
<div className={cn('flex items-center gap-2', className)}>
|
||||
<LogTypeIcon type={type} size={size} strokeWidth={strokeWidth} />
|
||||
<span>{type}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user