Files
Jonathan Summers-MuirandAlaister Young 1e3b5063bb Feat/unified logs inspection panel (#36895)
* Update QueryOptions.ts

* Create UnifiedLogs_InspectionPanel_Architecture.md

* init new queries

* Create UnifiedLogs_ServiceFlow_Implementation_Plan.md

* Add service flow blocks to UnifiedLogs panel

Introduces ServiceFlowBlocks components to visually represent the service flow in UnifiedLogs, including Origin, Network, PostgREST, Postgres, and Response steps. Adds utility formatters and updates ServiceFlowPanel to render the new blocks with enriched data and loading/error states.

* Enable filterable fields in ServiceFlow blocks

Refactored ServiceFlow blocks to support filterable fields by passing filterFields and table props, updating BlockField to render filterable values as clickable actions, and aligning field configs with filterable IDs. Updated ServiceFlowPanel to use new block names and pass required props. This enhances interactivity and consistency in the UnifiedLogs service flow UI.

* Update ServiceFlowBlocks.tsx

* Enhance Service Flow with enriched fields and filtering

Expanded the Service Flow SQL query to extract 35+ flattened, meaningful fields including request, response, client location, network, headers, and JWT data. Updated the implementation plan to reflect new block architecture, filtering integration, and future enhancements. Added debug logging for enriched and raw log data in ServiceFlowPanel.

* Start adding inspection panel

* Moar logs

* Add Postgres service flow support to Unified Logs

Introduces Postgres as a first-class service flow type in Unified Logs, including SQL query, type definitions, UI blocks, and filtering. Updates ServiceFlow queries, types, and UI components to display enriched Postgres log details and event messages. Also refines filter fields, log type ordering, and minor UI adjustments for clarity and consistency.

* Lots of tidying up, moving blocks into their own files.

* Remove UnifiedLogs architecture and service flow docs

Deleted documentation files related to UnifiedLogs inspection panel architecture, service flow field mapping, implementation plan, and integration example. This cleans up outdated or redundant design and implementation reference materials.

* Refactor service flow blocks to use unified config system

Replaces individual service block components with a generic, config-driven system. Introduces a shared Block component and centralizes block configuration in blockConfigs.ts, reducing duplication and improving maintainability. Updates imports and removes now-unnecessary block files. Also refactors ServiceFlowPanel to use the new block structure and simplifies layer icon logic.

* Refactor ServiceFlow blocks and utilities structure

Consolidates edge service flow SQL queries to reduce duplication, removes ServiceFlowBlocks.tsx in favor of more modular block/component structure, and moves storage-specific utilities to a new utils/storageUtils.ts file. Updates imports in config files and ServiceFlowPanel to reflect the new structure. Type definitions are clarified and extended in types.ts.

* Update ServiceFlow.sql.ts

* Refactor ServiceFlow types for stronger type safety

Replaces generic 'any' types with specific types such as ColumnSchema, QuerySearchParamsType, and SearchParamsType in ServiceFlow components and ServiceFlowPanel. This improves type safety and code clarity, and removes unused metadata and workaround variables from ServiceFlowPanel.

* Remove debug flags and console logs from ServiceFlow components

Eliminated unused DEBUG_SERVICE_FLOW flags and related console logging from ServiceFlow.sql.ts and ServiceFlowPanel.tsx to clean up the codebase and reduce unnecessary output.

* Remove unused log query and filtering functions

Deleted unused functions related to log level filtering, base condition building, and the edge/supavisor logs queries. This cleanup simplifies the UnifiedLogs.queries.ts file by removing legacy or currently unused code.

* revert

* Remove ServiceFlow panel and types, update DataTableToolbar

Deleted the ServiceFlowPanel component and its associated types from the UnifiedLogs interface. Updated DataTableToolbar to toggle between PanelLeftClose and PanelLeftOpen icons based on the open state.

* Remove unused totalRows prop from DataTable

Eliminates the totalRows prop from UnifiedLogs and its context interface, as it is no longer used. This helps clean up the component API and related type definitions.

* Refactor log field configs and unify event message component

Replaces specialized PostgresEventMessage with a generic EventMessage component and updates references accordingly. Consolidates all log field configuration files into serviceFlowFields.ts, removing separate config files for each service. Cleans up unused types and utility imports, and deletes redundant formatters.

* Show empty state for Postgres block with no data

Added logic to display an empty state in the Postgres block when no Postgres data is available in non-Postgres logs. Also updated ServiceFlowPanel to render both PostgREST and Postgres blocks, ensuring the empty state is shown when appropriate.

* Remove Log Details tab from ServiceFlowPanel

The Log Details tab and its associated content have been removed from the ServiceFlowPanel component. This simplifies the UI by focusing on the Overview and Raw JSON tabs.

* Update UnifiedLogs.queries.ts

* Update UnifiedLogs.queries.ts

* Update apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/shared/BlockField.tsx

* Refactor UnifiedLogs layout and improve service type formatting

Refactored UnifiedLogs.tsx to use a more flexible ResizablePanelGroup layout, replaced inline function logs panel with a new LogsListPanel component, and removed unused imports and code. Updated ServiceFlow.sql.ts to simplify JWT payload extraction and added a dedicated query for edge function service flow. Added a utility to format service type strings for display in UnifiedLogs.utils.ts.

* Refactor logs UI and improve service flow handling

Renamed FunctionLogsTab to LogsList and added a new LogsListPanel for displaying function logs in a side panel. Improved ServiceFlowPanel with better panel sizing, conditional query enabling, and UI adjustments. Enhanced EventMessage with badge styling and service type formatting. Updated CollapsibleSection for cleaner layout. Enabled debug mode in unified-log-inspection-query. Updated FilterSideBar to use ResizablePanel for improved layout flexibility.

* Refactor UnifiedLogs UI and ServiceFlow query fields

Removes unused skeletonClassName from BlockFieldConfig and related UI, simplifies ServiceFlow SQL query by returning null for unused JWT fields, and updates UnifiedLogs and LogsListPanel for improved layout and collapsible logs panel. Also adjusts table and log list row heights for better visual consistency.

* Update ServiceFlowHeader.tsx

* Update ServiceFlowHeader.tsx

* Refactor ServiceFlowPanel and header tooltip logic

Replaces custom tooltip implementation in ServiceFlowHeader with ButtonTooltip for navigation buttons. Simplifies serviceFlowType logic in ServiceFlowPanel by removing the getServiceFlowType helper and using direct mapping, reducing code complexity.

* Update UnifiedLogs.constants.tsx

* Remove commented code and add doc comment in UnifiedLogs

Removed an unused commented-out span in UnifiedLogs.fields.tsx and added a documentation comment for the Supabase storage logs query fragment in UnifiedLogs.queries.ts.

* Update UnifiedLogs.tsx

* Remove 'live' level from DataTable components

Eliminated the 'live' level from LEVELS constant and related color utility logic. Also removed commented code and references to 'live' in DataTableFilterControls and LiveRow, streamlining the DataTable component's status handling.

* Update UnifiedLogs.constants.tsx

* cleanup

---------

Co-authored-by: Alaister Young <a@alaisteryoung.com>
2025-07-08 20:00:23 +08:00

747 lines
22 KiB
TypeScript

import { formatBytes } from 'lib/helpers'
import { BlockFieldConfig } from '../types'
import { getStorageMetadata } from '../utils/storageUtils'
// 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
// =============================================================================
// 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[] = [
{
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: 'auth_path',
label: 'Auth Path',
getValue: (data, enrichedData) => {
return enrichedData?.path || enrichedData?.request_path || data?.path
},
requiresEnrichedData: true,
},
{
id: 'log_id',
label: 'Log ID',
getValue: (data, enrichedData) => {
const logId = data?.id || enrichedData?.id
return logId ? `${logId.substring(0, 8)}...` : null
},
},
{
id: 'referer',
label: 'Referer',
getValue: (data, enrichedData) => {
return enrichedData?.headers_referer || null
},
requiresEnrichedData: true,
},
]
// =============================================================================
// 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: '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,
},
]
// 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) => {
const sessionId = enrichedData?.session_id || data?.session_id
return sessionId ? `${sessionId.substring(0, 12)}...` : null
},
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,
},
]