import { useEffect, useState } from 'react'
import { toast } from 'sonner'
import {
Button,
cn,
copyToClipboard,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
Separator,
} from 'ui'
import { TimestampInfo } from 'ui-patterns'
import { ErrorCodeDialog } from '../ErrorCodeDialog'
import type { LogSearchCallback, PreviewLogData } from '../Logs.types'
import { ResponseCodeFormatter } from '../LogsFormatters'
import { ErrorCodeTooltip } from '@/components/ui/ErrorCodeTooltip/ErrorCodeTooltip'
import { Service } from '@/data/graphql/graphql'
import { useLogsUrlState } from '@/hooks/analytics/useLogsUrlState'
const LogRowCodeBlock = ({ value, className }: { value: string; className?: string }) => (
{typeof value === 'string' ? value : JSON.stringify(value, null, 2)}
)
const LogRowSeparator = () =>
const PropertyRow = ({
keyName,
value,
dataTestId,
path,
}: {
keyName: string
value: any
dataTestId?: string
path?: string
}) => {
const { setSearch } = useLogsUrlState()
const [showErrorInfo, setShowErrorInfo] = useState(false)
const service = path?.startsWith('/auth/') ? Service.Auth : undefined
const handleSearch: LogSearchCallback = async (_event: string, { query }: { query?: string }) => {
setSearch(query || '')
}
const isTimestamp =
keyName === 'timestamp' || keyName === 'created_at' || keyName === 'updated_at'
const isObject = typeof value === 'object' && value !== null
const isStatus = keyName === 'status' || keyName === 'status_code'
const isMethod = keyName === 'method'
const isSearch = keyName === 'search'
const isUserAgent = keyName === 'user_agent'
const isEventMessage = keyName === 'event_message'
const isPath = keyName === 'path'
const isErrorCode = keyName === 'error_code'
function getSearchPairs() {
if (isSearch && typeof value === 'string') {
const str = value.startsWith('?') ? value.slice(1) : value
return str.split('&').filter(Boolean)
}
return []
}
const storageKey = `log-viewer-expanded-${keyName}`
const [isExpanded, setIsExpanded] = useState(() => {
try {
// Storing in local storage so users dont have to click expand every time they change selected log
return JSON.parse(localStorage.getItem(storageKey) ?? 'false')
} catch (_) {
return false
}
})
const [isCopied, setIsCopied] = useState(false)
useEffect(() => {
localStorage.setItem(storageKey, JSON.stringify(isExpanded))
}, [isExpanded, storageKey])
const handleCopy = () => {
copyToClipboard(String(value), () => {
setIsCopied(true)
toast.success('Copied to clipboard')
})
setTimeout(() => {
setIsCopied(false)
}, 1000)
}
if (isObject || isEventMessage) {
return (
<>
{keyName}
{!isEventMessage && (
)}
>
)
}
return (
<>
{keyName}
{isExpanded ? (
) : isTimestamp ? (
) : isStatus ? (
) : isMethod ? (
) : isErrorCode ? (
{value}
) : (
{value}
)}
{keyName === 'error_code' && (
{
setShowErrorInfo(true)
}}
>
More information
)}
Copy {keyName}
{!isObject && (
{
setIsExpanded(!isExpanded)
}}
>
{isExpanded ? 'Collapse' : 'Expand'} value
)}
{(isMethod || isUserAgent || isStatus || isPath) && (
{
handleSearch('search-input-change', { query: value })
}}
>
Search by {keyName}
)}
{isSearch
? getSearchPairs().map((pair) => (
{
handleSearch('search-input-change', { query: pair })
}}
>
Search by {pair}
))
: null}
{keyName === 'error_code' && (
)}
>
)
}
const DefaultPreviewSelectionRenderer = ({ log }: { log: PreviewLogData }) => {
const { timestamp, event_message, metadata, id, status, ...rest } = log
const path = typeof log.path === 'string' ? log.path : undefined
const log_file = log?.metadata?.[0]?.log_file
return (
{log?.id && (
)}
{log?.status &&
}
{log?.timestamp && (
)}
{Object.entries(rest).map(([key, value]) => {
return
})}
{log?.event_message && (
)}
{!!log_file &&
}
{log?.metadata &&
}
)
}
export default DefaultPreviewSelectionRenderer