mirror of
https://github.com/supabase/supabase.git
synced 2026-09-09 03:19:36 +08:00
## Problem The unified log dashboards' Postgres detail panel hid the `query` and `detail` fields. They were present in the raw log message but never surfaced in the structured view, making them harder to use when debugging. ## Fix - Select `pgl_parsed.query` and `pgl_parsed.detail` in the Postgres service flow query. - Add `Query` and `Details` field configs to the Postgres primary fields, both with `wrap: true` so long values display in full instead of truncating. The parsed Postgres field is `detail` (singular); it is labeled "Details" in the UI. ## How to test - Open Studio and navigate to the unified logs dashboard for a project. - Filter to Postgres logs and select a log row to open the detail panel. - Confirm the Postgres section now shows `Query` and `Details` rows below `User`. - Expected result: rows render the parsed query and detail text, wrapping for long values, and show an em dash when empty. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * PostgreSQL service flow logs now show additional always-visible **Query** and **Details** fields, bringing parsed database query content and expanded information directly into the log view. * **Tests** * Updated log inspection coverage to ensure the new parsed fields are correctly surfaced in the flattened inspection output. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
753 lines
22 KiB
TypeScript
753 lines
22 KiB
TypeScript
import { BlockFieldConfig } from '../types'
|
|
import { getStorageMetadata } from '../utils/storageUtils'
|
|
import { formatBytes, tryParseJson } from '@/lib/helpers'
|
|
|
|
// Helper functions that avoid duplication with existing storage utilities
|
|
const getFileName = (path: string): string => {
|
|
if (!path) return ''
|
|
// Remove query parameters and hash
|
|
const cleanPath = path.split('?')[0].split('#')[0]
|
|
// Get the last part after the last slash
|
|
const segments = cleanPath.split('/')
|
|
return segments[segments.length - 1] || ''
|
|
}
|
|
|
|
const formatStorageDate = (dateString: string): string => {
|
|
try {
|
|
return new Date(dateString).toLocaleString()
|
|
} catch {
|
|
return dateString
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// NETWORK FIELDS
|
|
// =============================================================================
|
|
|
|
// Primary Network Fields (Always Visible) - FILTERABLE
|
|
export const networkPrimaryFields: BlockFieldConfig[] = [
|
|
{
|
|
id: 'host', // Matches filterFields 'host' (input) - FILTERABLE
|
|
label: 'Host',
|
|
getValue: (data, enrichedData) =>
|
|
enrichedData?.request_host || enrichedData?.host || data?.host,
|
|
},
|
|
{
|
|
id: 'method', // Matches filterFields 'method' (checkbox) - FILTERABLE
|
|
label: 'Method',
|
|
getValue: (data, enrichedData) =>
|
|
enrichedData?.request_method || enrichedData?.method || data?.method,
|
|
},
|
|
{
|
|
id: 'pathname', // Matches filterFields 'pathname' (input) - FILTERABLE
|
|
label: 'Path',
|
|
getValue: (data, enrichedData) =>
|
|
enrichedData?.request_path || enrichedData?.pathname || data?.pathname,
|
|
},
|
|
{
|
|
id: 'user_agent',
|
|
label: 'Client',
|
|
getValue: (_data, enrichedData) => {
|
|
const userAgent = enrichedData?.headers_user_agent
|
|
if (!userAgent) return null
|
|
// TODO: Parse user agent for nice display with icons
|
|
return userAgent.length > 50 ? userAgent.substring(0, 50) + '...' : userAgent
|
|
},
|
|
requiresEnrichedData: true,
|
|
},
|
|
]
|
|
|
|
// Primary API Key Field (Always Visible) - Shows API key type only
|
|
export const apiKeyPrimaryField: BlockFieldConfig = {
|
|
id: 'api_key_role',
|
|
label: 'API Key',
|
|
getValue: (_data, enrichedData) => {
|
|
const prefix = enrichedData?.api_key_prefix
|
|
const jwtRole = enrichedData?.jwt_key_role
|
|
const authRole = enrichedData?.authorization_role
|
|
|
|
if (prefix) {
|
|
// Extract key type from prefix
|
|
if (prefix.includes('publishable')) return 'publishable'
|
|
else if (prefix.includes('secret')) return 'secret'
|
|
return 'unknown'
|
|
}
|
|
|
|
// Fallback to JWT apikey postgres role if no API key prefix
|
|
if (jwtRole) {
|
|
return jwtRole
|
|
}
|
|
|
|
// Fallback to authorization postgres role if no apikey JWT
|
|
if (authRole) {
|
|
return authRole
|
|
}
|
|
|
|
return null
|
|
},
|
|
requiresEnrichedData: true,
|
|
}
|
|
|
|
// Additional API Key Fields (Collapsible)
|
|
export const apiKeyAdditionalFields: BlockFieldConfig[] = [
|
|
{
|
|
id: 'api_key_prefix_full',
|
|
label: 'API Key Prefix',
|
|
getValue: (_data, enrichedData) => {
|
|
const prefix = enrichedData?.api_key_prefix
|
|
return prefix || null
|
|
},
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'postgres_role',
|
|
label: 'Postgres Role',
|
|
getValue: (_data, enrichedData) => enrichedData?.jwt_key_role,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'api_key_error',
|
|
label: 'API Key Error',
|
|
getValue: (_data, enrichedData) => enrichedData?.api_key_error,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'api_key_hash',
|
|
label: 'API Key Hash',
|
|
getValue: (_data, enrichedData) => {
|
|
const hash = enrichedData?.api_key_hash
|
|
return hash ? `${hash.substring(0, 12)}...` : null
|
|
},
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'authorization_role',
|
|
label: 'Postgres Role',
|
|
getValue: (_data, enrichedData) => enrichedData?.authorization_role,
|
|
requiresEnrichedData: true,
|
|
},
|
|
]
|
|
|
|
// Primary User Field (Always Visible)
|
|
export const userPrimaryField: BlockFieldConfig = {
|
|
id: 'user_id',
|
|
label: 'User',
|
|
getValue: (_data, enrichedData) => {
|
|
const userId = enrichedData?.user_id
|
|
return userId ? `${userId.substring(0, 8)}...` : null
|
|
},
|
|
requiresEnrichedData: true,
|
|
}
|
|
|
|
// Additional User Fields (Collapsible)
|
|
export const userAdditionalFields: BlockFieldConfig[] = [
|
|
{
|
|
id: 'auth_user', // Matches filterFields 'auth_user' (input) - FILTERABLE
|
|
label: 'Auth User',
|
|
getValue: (data, enrichedData) => enrichedData?.auth_user || data?.auth_user,
|
|
},
|
|
{
|
|
id: 'user_email',
|
|
label: 'User Email',
|
|
getValue: (_data, enrichedData) => enrichedData?.user_email,
|
|
requiresEnrichedData: true,
|
|
},
|
|
]
|
|
|
|
// Primary Location Field (Always Visible)
|
|
export const locationPrimaryField: BlockFieldConfig = {
|
|
id: 'client_country',
|
|
label: 'Location',
|
|
getValue: (_data, enrichedData) => {
|
|
const country = enrichedData?.client_country || enrichedData?.cf_country
|
|
const city = enrichedData?.client_city
|
|
if (country && city) return `${city}, ${country}`
|
|
if (country) return country
|
|
if (city) return city
|
|
return null
|
|
},
|
|
requiresEnrichedData: true,
|
|
}
|
|
|
|
// Additional Location Fields (Collapsible) - includes IP addresses
|
|
export const locationAdditionalFields: BlockFieldConfig[] = [
|
|
{
|
|
id: 'client_continent',
|
|
label: 'Continent',
|
|
getValue: (_data, enrichedData) => enrichedData?.client_continent,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'client_region',
|
|
label: 'Region',
|
|
getValue: (_data, enrichedData) => enrichedData?.client_region,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'client_timezone',
|
|
label: 'Timezone',
|
|
getValue: (_data, enrichedData) => enrichedData?.client_timezone,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'x_real_ip',
|
|
label: 'Real IP',
|
|
getValue: (_data, enrichedData) => enrichedData?.headers_x_real_ip,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'client_ip',
|
|
label: 'Client IP',
|
|
getValue: (_data, enrichedData) => enrichedData?.client_ip,
|
|
requiresEnrichedData: true,
|
|
},
|
|
]
|
|
|
|
// Authorization Fields (Collapsible) - JWT authorization details
|
|
export const authorizationFields: BlockFieldConfig[] = [
|
|
{
|
|
id: 'jwt_auth_key_id',
|
|
label: 'Key ID',
|
|
getValue: (_data, enrichedData) =>
|
|
enrichedData?.jwt_auth_key_id || enrichedData?.jwt_apikey_key_id,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'jwt_auth_session_id',
|
|
label: 'Session ID',
|
|
getValue: (_data, enrichedData) =>
|
|
enrichedData?.jwt_auth_session_id || enrichedData?.jwt_apikey_session_id,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'jwt_auth_subject',
|
|
label: 'Subject',
|
|
getValue: (_data, enrichedData) =>
|
|
enrichedData?.jwt_auth_subject || enrichedData?.jwt_apikey_subject,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'jwt_auth_issuer',
|
|
label: 'Issuer',
|
|
getValue: (_data, enrichedData) =>
|
|
enrichedData?.jwt_auth_issuer || enrichedData?.jwt_apikey_issuer,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'jwt_auth_algorithm',
|
|
label: 'Algorithm',
|
|
getValue: (_data, enrichedData) =>
|
|
enrichedData?.jwt_auth_algorithm || enrichedData?.jwt_apikey_algorithm,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'jwt_auth_expires_at',
|
|
label: 'Expires At',
|
|
getValue: (_data, enrichedData) => {
|
|
const expiresAt = enrichedData?.jwt_auth_expires_at || enrichedData?.jwt_apikey_expires_at
|
|
if (!expiresAt) return null
|
|
try {
|
|
return new Date(expiresAt * 1000).toLocaleString()
|
|
} catch {
|
|
return expiresAt
|
|
}
|
|
},
|
|
requiresEnrichedData: true,
|
|
},
|
|
]
|
|
|
|
// Tech Details Fields (Collapsible)
|
|
export const techDetailsFields: BlockFieldConfig[] = [
|
|
{
|
|
id: 'network_protocol',
|
|
label: 'Protocol',
|
|
getValue: (_data, enrichedData) => enrichedData?.network_protocol,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'cf_datacenter',
|
|
label: 'Datacenter',
|
|
getValue: (_data, enrichedData) =>
|
|
enrichedData?.network_datacenter || enrichedData?.cf_datacenter,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'cache_status',
|
|
label: 'Cache Status',
|
|
getValue: (_data, enrichedData) => enrichedData?.response_cache_status,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'cf_ray',
|
|
label: 'CF-Ray',
|
|
getValue: (_data, enrichedData) => enrichedData?.cf_ray,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'x_client_info',
|
|
label: 'SDK',
|
|
getValue: (_data, enrichedData) => enrichedData?.headers_x_client_info,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'x_forwarded_proto',
|
|
label: 'Forwarded Proto',
|
|
getValue: (_data, enrichedData) => enrichedData?.headers_x_forwarded_proto,
|
|
requiresEnrichedData: true,
|
|
},
|
|
]
|
|
|
|
// =============================================================================
|
|
// POSTGREST FIELDS
|
|
// =============================================================================
|
|
|
|
// Primary PostgREST Fields (Always Visible) - FILTERABLE
|
|
export const postgrestPrimaryFields: BlockFieldConfig[] = [
|
|
{
|
|
id: 'status', // Matches filterFields 'status' (checkbox) - FILTERABLE
|
|
label: 'Status',
|
|
getValue: (data, enrichedData) => enrichedData?.status || data?.status,
|
|
},
|
|
{
|
|
id: 'postgres_role',
|
|
label: 'Postgres Role',
|
|
getValue: (_data, enrichedData) => enrichedData?.api_role,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'response_time',
|
|
label: 'Response Time',
|
|
getValue: (data, enrichedData) => {
|
|
const time = enrichedData?.response_origin_time || data?.response_time_ms
|
|
return time ? `${time}ms` : null
|
|
},
|
|
requiresEnrichedData: true,
|
|
},
|
|
]
|
|
|
|
// PostgREST Response Details (Collapsible)
|
|
export const postgrestResponseFields: BlockFieldConfig[] = [
|
|
{
|
|
id: 'query_params',
|
|
label: 'Query',
|
|
getValue: (_data, enrichedData) => enrichedData?.request_search,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'content_type',
|
|
label: 'Content Type',
|
|
getValue: (_data, enrichedData) => enrichedData?.response_content_type,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'message',
|
|
label: 'Message',
|
|
getValue: (_data, enrichedData) => enrichedData?.message,
|
|
requiresEnrichedData: true,
|
|
},
|
|
]
|
|
|
|
// =============================================================================
|
|
// AUTH FIELDS
|
|
// =============================================================================
|
|
|
|
// Primary GoTrue/Auth Fields (Always Visible)
|
|
export const authPrimaryFields: BlockFieldConfig[] = [
|
|
{
|
|
id: 'log_id',
|
|
label: 'Log ID',
|
|
getValue: (data, enrichedData) => {
|
|
const logId = data?.id || enrichedData?.id
|
|
return logId ?? null
|
|
},
|
|
},
|
|
{
|
|
id: 'status',
|
|
label: 'Status',
|
|
getValue: (data) => {
|
|
return data.status
|
|
},
|
|
},
|
|
{
|
|
id: 'auth_path',
|
|
label: 'Auth Path',
|
|
getValue: (data, enrichedData) => {
|
|
return enrichedData?.path || enrichedData?.request_path || data?.path || data?.pathname
|
|
},
|
|
},
|
|
{
|
|
id: 'referer',
|
|
label: 'Referer',
|
|
getValue: (data, enrichedData) => {
|
|
const eventMessage = tryParseJson(data.event_message)
|
|
return eventMessage?.referer || enrichedData?.headers_referer || null
|
|
},
|
|
},
|
|
]
|
|
|
|
// =============================================================================
|
|
// EDGE FUNCTION FIELDS
|
|
// =============================================================================
|
|
|
|
// Primary Edge Function Fields (Always Visible)
|
|
export const edgeFunctionPrimaryFields: BlockFieldConfig[] = [
|
|
{
|
|
id: 'status',
|
|
label: 'Status',
|
|
getValue: (data, enrichedData) => enrichedData?.status || data?.status,
|
|
},
|
|
{
|
|
id: 'function_path',
|
|
label: 'Function Path',
|
|
getValue: (data, enrichedData) => {
|
|
return (
|
|
enrichedData?.path || enrichedData?.request_path || data?.path || enrichedData?.request_url
|
|
)
|
|
},
|
|
requiresEnrichedData: false,
|
|
},
|
|
{
|
|
id: 'execution_time',
|
|
label: 'Execution Time',
|
|
getValue: (data, enrichedData) => {
|
|
const time = enrichedData?.execution_time_ms || data?.execution_time_ms
|
|
return time ? `${time}ms` : null
|
|
},
|
|
requiresEnrichedData: false,
|
|
},
|
|
{
|
|
id: 'execution_id',
|
|
label: 'Execution ID',
|
|
getValue: (data, enrichedData) => {
|
|
const execId = enrichedData?.execution_id || data?.execution_id
|
|
return execId ? `${execId.substring(0, 8)}...` : null
|
|
},
|
|
requiresEnrichedData: false,
|
|
},
|
|
]
|
|
|
|
// Edge Function Details (Collapsible)
|
|
export const edgeFunctionDetailsFields: BlockFieldConfig[] = [
|
|
{
|
|
id: 'function_id',
|
|
label: 'Function ID',
|
|
getValue: (data, enrichedData) => {
|
|
const funcId = enrichedData?.function_id || data?.function_id
|
|
return funcId ? `${funcId.substring(0, 8)}...` : null
|
|
},
|
|
requiresEnrichedData: false,
|
|
},
|
|
{
|
|
id: 'execution_region',
|
|
label: 'Execution Region',
|
|
getValue: (data, enrichedData) => {
|
|
return enrichedData?.execution_region || data?.execution_region || null
|
|
},
|
|
requiresEnrichedData: false,
|
|
},
|
|
{
|
|
id: 'function_log_count',
|
|
label: 'Function Logs',
|
|
getValue: (data, enrichedData) => {
|
|
const count = enrichedData?.function_log_count || data?.function_log_count || data?.log_count
|
|
return count ? `${count} logs` : 'No logs'
|
|
},
|
|
requiresEnrichedData: false,
|
|
},
|
|
{
|
|
id: 'method',
|
|
label: 'Method',
|
|
getValue: (data, enrichedData) => enrichedData?.method || data?.method,
|
|
requiresEnrichedData: false,
|
|
},
|
|
{
|
|
id: 'user_agent',
|
|
label: 'User Agent',
|
|
getValue: (data, enrichedData) => {
|
|
const ua = enrichedData?.headers_user_agent || data?.headers_user_agent
|
|
return ua ? (ua.length > 30 ? `${ua.substring(0, 30)}...` : ua) : null
|
|
},
|
|
requiresEnrichedData: false,
|
|
},
|
|
]
|
|
|
|
// =============================================================================
|
|
// STORAGE FIELDS
|
|
// =============================================================================
|
|
|
|
// Primary Storage Fields (Always Visible)
|
|
export const storagePrimaryFields: BlockFieldConfig[] = [
|
|
{
|
|
id: 'status',
|
|
label: 'Status',
|
|
getValue: (data, enrichedData) => enrichedData?.status || data?.status,
|
|
},
|
|
{
|
|
id: 'filename',
|
|
label: 'File Name',
|
|
getValue: (data, enrichedData) => {
|
|
const path = enrichedData?.path || enrichedData?.request_path || data?.path
|
|
return path ? getFileName(path) : null
|
|
},
|
|
requiresEnrichedData: false,
|
|
},
|
|
{
|
|
id: 'content_type_size',
|
|
label: 'Type & Size',
|
|
getValue: (data, enrichedData) => {
|
|
const status = enrichedData?.status || data?.status
|
|
const isObjectDeleted = status === 404 || status === '404'
|
|
const hasError = status && Number(status) >= 400
|
|
|
|
// For deleted objects, show status message
|
|
if (isObjectDeleted) {
|
|
return 'Object deleted'
|
|
} else if (hasError) {
|
|
return `Error ${status}`
|
|
}
|
|
|
|
// Try to get metadata like PreviewPane does
|
|
const metadata = getStorageMetadata(data, enrichedData)
|
|
const mimeType = metadata?.mimetype
|
|
const size = metadata?.size
|
|
|
|
// Fallback to headers if metadata not available
|
|
const contentType =
|
|
mimeType ||
|
|
enrichedData?.response_content_type ||
|
|
enrichedData?.storage_request_content_type
|
|
const contentLength =
|
|
size ||
|
|
(enrichedData?.storage_content_length
|
|
? parseInt(enrichedData.storage_content_length)
|
|
: null)
|
|
|
|
if (contentType && contentLength) {
|
|
return `${contentType} - ${formatBytes(contentLength)}`
|
|
} else if (contentType) {
|
|
return contentType
|
|
} else if (contentLength) {
|
|
return formatBytes(contentLength)
|
|
}
|
|
return 'Unknown type'
|
|
},
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'response_time',
|
|
label: 'Response Time',
|
|
getValue: (data, enrichedData) => {
|
|
const time = enrichedData?.response_origin_time || data?.response_time_ms
|
|
return time ? `${time}ms` : null
|
|
},
|
|
requiresEnrichedData: true,
|
|
},
|
|
]
|
|
|
|
// Storage Details (Collapsible)
|
|
export const storageDetailsFields: BlockFieldConfig[] = [
|
|
{
|
|
id: 'storage_path',
|
|
label: 'Storage Path',
|
|
getValue: (data, enrichedData) => {
|
|
const path = enrichedData?.path || enrichedData?.request_path || data?.path
|
|
// Extract just the object path from the full storage path
|
|
const match = path?.match(/\/storage\/v1\/object\/([^\/]+)\/(.+)/)
|
|
if (match) {
|
|
const [, bucketName, objectPath] = match
|
|
return `${bucketName}/${objectPath}`
|
|
}
|
|
return path
|
|
},
|
|
requiresEnrichedData: false,
|
|
},
|
|
{
|
|
id: 'last_modified',
|
|
label: 'Last Modified',
|
|
getValue: (data, enrichedData) => {
|
|
const status = enrichedData?.status || data?.status
|
|
const isObjectDeleted = status === 404 || status === '404'
|
|
const hasError = status && Number(status) >= 400
|
|
|
|
if (isObjectDeleted) {
|
|
return 'Object deleted'
|
|
} else if (hasError) {
|
|
return 'Unavailable'
|
|
}
|
|
|
|
// Try to get dates from metadata like PreviewPane does
|
|
const metadata = getStorageMetadata(data, enrichedData)
|
|
const updatedAt = metadata?.updated_at || data?.updated_at || enrichedData?.updated_at
|
|
const lastModified = enrichedData?.storage_last_modified || updatedAt
|
|
|
|
return lastModified ? formatStorageDate(lastModified) : null
|
|
},
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'etag',
|
|
label: 'ETag',
|
|
getValue: (data, enrichedData) => {
|
|
const status = enrichedData?.status || data?.status
|
|
const isObjectDeleted = status === 404 || status === '404'
|
|
const hasError = status && Number(status) >= 400
|
|
|
|
if (isObjectDeleted) {
|
|
return 'Object deleted'
|
|
} else if (hasError) {
|
|
return 'Unavailable'
|
|
}
|
|
|
|
const etag = enrichedData?.storage_etag
|
|
return etag ? `${etag.substring(0, 12)}...` : null
|
|
},
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'content_disposition',
|
|
label: 'Content Disposition',
|
|
getValue: (data, enrichedData) => {
|
|
const status = enrichedData?.status || data?.status
|
|
const isObjectDeleted = status === 404 || status === '404'
|
|
const hasError = status && Number(status) >= 400
|
|
|
|
if (isObjectDeleted) {
|
|
return 'Object deleted'
|
|
} else if (hasError) {
|
|
return 'Unavailable'
|
|
}
|
|
|
|
return enrichedData?.storage_content_disposition || null
|
|
},
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'method',
|
|
label: 'Method',
|
|
getValue: (data, enrichedData) => enrichedData?.method || data?.method,
|
|
requiresEnrichedData: false,
|
|
},
|
|
{
|
|
id: 'user_agent',
|
|
label: 'User Agent',
|
|
getValue: (data, enrichedData) => {
|
|
const ua = enrichedData?.headers_user_agent || data?.headers_user_agent
|
|
return ua ? (ua.length > 30 ? `${ua.substring(0, 30)}...` : ua) : null
|
|
},
|
|
requiresEnrichedData: false,
|
|
},
|
|
]
|
|
|
|
// =============================================================================
|
|
// POSTGRES FIELDS
|
|
// =============================================================================
|
|
|
|
// Primary Postgres Fields (Always Visible)
|
|
export const postgresPrimaryFields: BlockFieldConfig[] = [
|
|
{
|
|
id: 'event_message',
|
|
label: 'Message',
|
|
getValue: (data, enrichedData) => enrichedData?.event_message || data?.event_message,
|
|
wrap: true,
|
|
},
|
|
{
|
|
id: 'status',
|
|
label: 'Status',
|
|
getValue: (data, enrichedData) => enrichedData?.status || data?.status,
|
|
},
|
|
{
|
|
id: 'command_tag',
|
|
label: 'Command',
|
|
getValue: (data, enrichedData) => enrichedData?.command_tag || data?.command_tag,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'database_name',
|
|
label: 'Database',
|
|
getValue: (data, enrichedData) => enrichedData?.database_name || data?.database_name,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'database_user',
|
|
label: 'User',
|
|
getValue: (data, enrichedData) => enrichedData?.database_user || data?.database_user,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'query',
|
|
label: 'Query',
|
|
getValue: (data, enrichedData) => enrichedData?.query || data?.query,
|
|
requiresEnrichedData: true,
|
|
wrap: true,
|
|
},
|
|
{
|
|
id: 'detail',
|
|
label: 'Details',
|
|
getValue: (data, enrichedData) => enrichedData?.detail || data?.detail,
|
|
requiresEnrichedData: true,
|
|
wrap: true,
|
|
},
|
|
]
|
|
|
|
// Postgres Details (Collapsible)
|
|
export const postgresDetailsFields: BlockFieldConfig[] = [
|
|
{
|
|
id: 'backend_type',
|
|
label: 'Backend Type',
|
|
getValue: (data, enrichedData) => enrichedData?.backend_type || data?.backend_type,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'connection_from',
|
|
label: 'Connection From',
|
|
getValue: (data, enrichedData) => enrichedData?.connection_from || data?.connection_from,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'session_id',
|
|
label: 'Session ID',
|
|
getValue: (data, enrichedData) => enrichedData?.session_id || data?.session_id,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'process_id',
|
|
label: 'Process ID',
|
|
getValue: (data, enrichedData) => enrichedData?.process_id || data?.process_id,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'query_id',
|
|
label: 'Query ID',
|
|
getValue: (data, enrichedData) => {
|
|
const queryId = enrichedData?.query_id || data?.query_id
|
|
return queryId ? String(queryId) : null
|
|
},
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'transaction_id',
|
|
label: 'Transaction ID',
|
|
getValue: (data, enrichedData) => {
|
|
const txId = enrichedData?.transaction_id || data?.transaction_id
|
|
return txId ? String(txId) : null
|
|
},
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'virtual_transaction_id',
|
|
label: 'Virtual TX ID',
|
|
getValue: (data, enrichedData) =>
|
|
enrichedData?.virtual_transaction_id || data?.virtual_transaction_id,
|
|
requiresEnrichedData: true,
|
|
},
|
|
{
|
|
id: 'session_start_time',
|
|
label: 'Session Started',
|
|
getValue: (data, enrichedData) => {
|
|
const startTime = enrichedData?.session_start_time || data?.session_start_time
|
|
return startTime ? new Date(startTime).toLocaleString() : null
|
|
},
|
|
requiresEnrichedData: true,
|
|
},
|
|
]
|