diff --git a/apps/studio/components/interfaces/Reports/Reports.constants.ts b/apps/studio/components/interfaces/Reports/Reports.constants.ts index 7871ef0a1880b..3f917f04db0a6 100644 --- a/apps/studio/components/interfaces/Reports/Reports.constants.ts +++ b/apps/studio/components/interfaces/Reports/Reports.constants.ts @@ -149,7 +149,7 @@ export const PRESET_CONFIG: Record = { queries: { totalRequests: { queryType: 'logs', - sql: (filters) => safeLogSql` + safeSql: (filters) => safeLogSql` -- reports-api-total-requests select cast(timestamp_trunc(t.timestamp, hour) as datetime) as timestamp, @@ -167,7 +167,7 @@ export const PRESET_CONFIG: Record = { }, topRoutes: { queryType: 'logs', - sql: (filters) => safeLogSql` + safeSql: (filters) => safeLogSql` -- reports-api-top-routes select request.path as path, @@ -190,7 +190,7 @@ export const PRESET_CONFIG: Record = { }, errorCounts: { queryType: 'logs', - sql: (filters) => safeLogSql` + safeSql: (filters) => safeLogSql` -- reports-api-error-counts select cast(timestamp_trunc(t.timestamp, hour) as datetime) as timestamp, @@ -211,7 +211,7 @@ export const PRESET_CONFIG: Record = { }, topErrorRoutes: { queryType: 'logs', - sql: (filters) => safeLogSql` + safeSql: (filters) => safeLogSql` -- reports-api-top-error-routes select request.path as path, @@ -236,7 +236,7 @@ export const PRESET_CONFIG: Record = { }, responseSpeed: { queryType: 'logs', - sql: (filters) => safeLogSql` + safeSql: (filters) => safeLogSql` -- reports-api-response-speed select cast(timestamp_trunc(t.timestamp, hour) as datetime) as timestamp, @@ -256,7 +256,7 @@ export const PRESET_CONFIG: Record = { }, topSlowRoutes: { queryType: 'logs', - sql: (filters) => safeLogSql` + safeSql: (filters) => safeLogSql` -- reports-api-top-slow-routes select request.path as path, @@ -280,7 +280,7 @@ export const PRESET_CONFIG: Record = { }, networkTraffic: { queryType: 'logs', - sql: (filters) => safeLogSql` + safeSql: (filters) => safeLogSql` -- reports-api-network-traffic select cast(timestamp_trunc(t.timestamp, hour) as datetime) as timestamp, @@ -318,7 +318,7 @@ export const PRESET_CONFIG: Record = { }, requestsByCountry: { queryType: 'logs', - sql: (filters) => safeLogSql` + safeSql: (filters) => safeLogSql` -- reports-api-requests-by-country select cf.country as country, @@ -348,7 +348,7 @@ export const PRESET_CONFIG: Record = { cacheHitRate: { queryType: 'logs', // storage report does not perform any filtering - sql: (filters) => safeLogSql` + safeSql: (filters) => safeLogSql` -- reports-storage-cache-hit-rate SELECT timestamp_trunc(timestamp, hour) as timestamp, @@ -368,7 +368,7 @@ order by timestamp desc topCacheMisses: { queryType: 'logs', // storage report does not perform any filtering - sql: (filters) => safeLogSql` + safeSql: (filters) => safeLogSql` -- reports-storage-top-cache-misses SELECT r.path as path, diff --git a/apps/studio/components/interfaces/Reports/Reports.types.ts b/apps/studio/components/interfaces/Reports/Reports.types.ts index 33fb035fcface..f4c510296ab60 100644 --- a/apps/studio/components/interfaces/Reports/Reports.types.ts +++ b/apps/studio/components/interfaces/Reports/Reports.types.ts @@ -24,7 +24,7 @@ export type BaseQueries = Record export interface ReportQueryLogs { queryType: 'logs' - sql: ( + safeSql: ( filters: ReportFilterItem[], where?: string, orderBy?: string, diff --git a/apps/studio/components/interfaces/Reports/Reports.utils.tsx b/apps/studio/components/interfaces/Reports/Reports.utils.tsx index 064c3ee9ec9e8..46ab235753edc 100644 --- a/apps/studio/components/interfaces/Reports/Reports.utils.tsx +++ b/apps/studio/components/interfaces/Reports/Reports.utils.tsx @@ -54,7 +54,7 @@ export function getLogsSql(query: ReportQuery, filters: ReportFilterItem[]): Saf if (query.queryType !== 'logs') { throw new Error(`Expected logs query, got ${query.queryType}`) } - return query.sql(filters) + return query.safeSql(filters) } /** diff --git a/apps/studio/components/interfaces/Reports/SharedAPIReport/SharedAPIReport.constants.ts b/apps/studio/components/interfaces/Reports/SharedAPIReport/SharedAPIReport.constants.ts index 72f7006907a1f..06d3ca0e3da68 100644 --- a/apps/studio/components/interfaces/Reports/SharedAPIReport/SharedAPIReport.constants.ts +++ b/apps/studio/components/interfaces/Reports/SharedAPIReport/SharedAPIReport.constants.ts @@ -22,7 +22,7 @@ function sourceTable(src: string): SafeLogSqlFragment { export const SHARED_API_REPORT_SQL = { totalRequests: { queryType: 'logs', - sql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment => + safeSql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment => safeSql` --reports-api-total-requests select @@ -41,7 +41,7 @@ export const SHARED_API_REPORT_SQL = { }, topRoutes: { queryType: 'logs', - sql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment => + safeSql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment => safeSql` -- reports-api-top-routes select @@ -65,7 +65,7 @@ export const SHARED_API_REPORT_SQL = { }, errorCounts: { queryType: 'logs', - sql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment => + safeSql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment => safeSql` -- reports-api-error-counts select @@ -87,7 +87,7 @@ export const SHARED_API_REPORT_SQL = { }, topErrorRoutes: { queryType: 'logs', - sql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment => + safeSql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment => safeSql` -- reports-api-top-error-routes select @@ -113,7 +113,7 @@ export const SHARED_API_REPORT_SQL = { }, responseSpeed: { queryType: 'logs', - sql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment => + safeSql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment => safeSql` -- reports-api-response-speed select @@ -134,7 +134,7 @@ export const SHARED_API_REPORT_SQL = { }, topSlowRoutes: { queryType: 'logs', - sql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment => + safeSql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment => safeSql` -- reports-api-top-slow-routes select @@ -159,7 +159,7 @@ export const SHARED_API_REPORT_SQL = { }, networkTraffic: { queryType: 'logs', - sql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment => + safeSql: (filters: ReportFilterItem[], src = 'edge_logs'): SafeLogSqlFragment => safeSql` -- reports-api-network-traffic select @@ -269,7 +269,7 @@ export const useSharedAPIReport = ({ const data = await executeAnalyticsSql({ projectRef: ref, endpoint: '/platform/projects/{ref}/analytics/endpoints/logs.all', - sql: value.sql(allFilters, filterByMapSource[filterBy]), + sql: value.safeSql(allFilters, filterByMapSource[filterBy]), iso_timestamp_start: start, iso_timestamp_end: end, method: 'get', @@ -334,16 +334,25 @@ export const useSharedAPIReport = ({ const isLoadingData = Object.values(isLoading).some(Boolean) const SQLMap: Record = { - totalRequests: SHARED_API_REPORT_SQL.totalRequests.sql(allFilters, filterByMapSource[filterBy]), - topRoutes: SHARED_API_REPORT_SQL.topRoutes.sql(allFilters, filterByMapSource[filterBy]), - errorCounts: SHARED_API_REPORT_SQL.errorCounts.sql(allFilters, filterByMapSource[filterBy]), - topErrorRoutes: SHARED_API_REPORT_SQL.topErrorRoutes.sql( + totalRequests: SHARED_API_REPORT_SQL.totalRequests.safeSql( allFilters, filterByMapSource[filterBy] ), - responseSpeed: SHARED_API_REPORT_SQL.responseSpeed.sql(allFilters, filterByMapSource[filterBy]), - topSlowRoutes: SHARED_API_REPORT_SQL.topSlowRoutes.sql(allFilters, filterByMapSource[filterBy]), - networkTraffic: SHARED_API_REPORT_SQL.networkTraffic.sql( + topRoutes: SHARED_API_REPORT_SQL.topRoutes.safeSql(allFilters, filterByMapSource[filterBy]), + errorCounts: SHARED_API_REPORT_SQL.errorCounts.safeSql(allFilters, filterByMapSource[filterBy]), + topErrorRoutes: SHARED_API_REPORT_SQL.topErrorRoutes.safeSql( + allFilters, + filterByMapSource[filterBy] + ), + responseSpeed: SHARED_API_REPORT_SQL.responseSpeed.safeSql( + allFilters, + filterByMapSource[filterBy] + ), + topSlowRoutes: SHARED_API_REPORT_SQL.topSlowRoutes.safeSql( + allFilters, + filterByMapSource[filterBy] + ), + networkTraffic: SHARED_API_REPORT_SQL.networkTraffic.safeSql( allFilters, filterByMapSource[filterBy] ), diff --git a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/blocks/PostgresFlowDetail.tsx b/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/PostgresFlowDetail.tsx similarity index 93% rename from apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/blocks/PostgresFlowDetail.tsx rename to apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/PostgresFlowDetail.tsx index 8dce7856b6a34..7e2dca023eac4 100644 --- a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/blocks/PostgresFlowDetail.tsx +++ b/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/PostgresFlowDetail.tsx @@ -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 { diff --git a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/blocks/RequestStartedBlock.tsx b/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/blocks/RequestStartedBlock.tsx deleted file mode 100644 index 4982d9c36281f..0000000000000 --- a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/blocks/RequestStartedBlock.tsx +++ /dev/null @@ -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 ( -
-
-
- - Request started -
- {formattedTime && ( - {formattedTime} - )} -
- {/* Connecting line to first timeline block */} -
-
- ) -}) - -MemoizedRequestStartedBlock.displayName = 'MemoizedRequestStartedBlock' diff --git a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/blocks/ResponseCompletedBlock.tsx b/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/blocks/ResponseCompletedBlock.tsx deleted file mode 100644 index e7a88aff82dd3..0000000000000 --- a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/blocks/ResponseCompletedBlock.tsx +++ /dev/null @@ -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 -} - -// 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 ( -
-
-
- - {isPostgresLog ? 'Operation Result' : 'Response'} -
- - {/* Status display */} - {isPostgresLog - ? // Postgres status - severity && ( - - {hasPostgresError ? `${severity.toUpperCase()} Error` : `${severity.toUpperCase()}`} - - ) - : // HTTP status - data?.status && ( - - {hasError ? `${data.status} Error` : `${data.status} Success`} - - )} -
- - {/* Completion message */} - {isPostgresLog ? ( - // Postgres completion message -
- {hasPostgresError - ? 'Database operation completed with error' - : 'Database operation completed successfully'} -
- ) : ( - // HTTP completion message - responseTime && ( -
- {hasError - ? `Error response sent to client in ${responseTime}ms` - : `Response sent to client in ${responseTime}ms`} -
- ) - )} - - {/* Error/Event details */} - {isPostgresLog - ? // Show postgres event message - eventMessage && ( -
- -
- ) - : // Show HTTP error details - hasError && ( -
-
Error Details
-
- {status >= 500 - ? 'Server error occurred during request processing' - : status >= 400 - ? 'Client error - check request parameters and authentication' - : 'Request completed with error status'} -
-
- )} -
- ) -}) - -MemoizedResponseCompletedBlock.displayName = 'MemoizedResponseCompletedBlock' diff --git a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/shared/Block.tsx b/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/shared/Block.tsx index cc6550ea09d37..7bc210c075b87 100644 --- a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/shared/Block.tsx +++ b/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/shared/Block.tsx @@ -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[] diff --git a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/shared/CollapsibleSection.tsx b/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/shared/CollapsibleSection.tsx deleted file mode 100644 index 27ef2f97c2afd..0000000000000 --- a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/shared/CollapsibleSection.tsx +++ /dev/null @@ -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[] - table: Table - defaultOpen?: boolean -} - -export const CollapsibleSection = ({ - title, - fields, - data, - enrichedData, - isLoading, - filterFields, - table, - defaultOpen = false, -}: CollapsibleSectionProps) => { - const [isOpen, setIsOpen] = useState(defaultOpen) - - return ( -
- - - - - - {fields.map((field) => ( - - ))} - - -
- ) -} diff --git a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/shared/EventMessage.tsx b/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/shared/EventMessage.tsx deleted file mode 100644 index ab189bf2a726b..0000000000000 --- a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/shared/EventMessage.tsx +++ /dev/null @@ -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 ( -
-
- - - {formatServiceTypeForDisplay(serviceType)} Event - {severity && ( - - {severity} - - )} - -
-
-
- {message} -
-
-
- ) -} diff --git a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/shared/TimelineStep.tsx b/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/shared/TimelineStep.tsx deleted file mode 100644 index b179152d44822..0000000000000 --- a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/components/shared/TimelineStep.tsx +++ /dev/null @@ -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 - -// Reusable styled icon component -export const StyledIcon = ({ - icon: Icon, - title: _title, -}: { - icon: IconComponent - title: string -}) => ( -
- -
-) - -export const TimelineStep = ({ - title, - status, - statusText, - children, - isLast = false, -}: { - title: string - status?: number | string - statusText?: string - children: React.ReactNode - isLast?: boolean -}) => ( - <> -
- {/* Timeline dot - positioned on the left timeline line */} -
-
-
-
- {title === 'Request started' && } - {title === 'Network' && } - {title === 'Data API' && } - {title === 'Authentication' && } - {title === 'Edge Function' && } - {title === 'Storage' && } - {title === 'Postgres' && } - {title === 'Response' && } -

{title}

-
- - {statusText && ( - {statusText} - )} - - {status && ( - - )} -
-
-
- - {/* Main section box */} -
- {/* Content */} -
{children}
-
- {!isLast &&
} -
- -) diff --git a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/config/serviceFlowFields.ts b/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/config/serviceFlowFields.ts index 95c3227835c68..1765a13e5188f 100644 --- a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/config/serviceFlowFields.ts +++ b/apps/studio/components/interfaces/UnifiedLogs/ServiceFlow/config/serviceFlowFields.ts @@ -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[] = [ { diff --git a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlowPanel.tsx b/apps/studio/components/interfaces/UnifiedLogs/ServiceFlowPanel.tsx index 63e17c0ada9ed..c4a7723148a71 100644 --- a/apps/studio/components/interfaces/UnifiedLogs/ServiceFlowPanel.tsx +++ b/apps/studio/components/interfaces/UnifiedLogs/ServiceFlowPanel.tsx @@ -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, diff --git a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.fields.tsx b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.fields.tsx index c13ecbbbf5f1f..ef131636e14f2 100644 --- a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.fields.tsx +++ b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.fields.tsx @@ -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 ( -
- {month} - · - {day} - · - {year} - · - {time} -
- ) -} // 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[] - -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) => ( -
- - {props.auth_user} -
- ), - skeletonClassName: 'w-56', - }, - { - id: 'pathname', - label: 'Pathname', - type: 'input', - skeletonClassName: 'w-56', - }, -] satisfies SheetField[] diff --git a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.queries.bq.ts b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.queries.bq.ts index 120d5861f11da..3aff9b46fd1ec 100644 --- a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.queries.bq.ts +++ b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.queries.bq.ts @@ -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') diff --git a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.types.ts b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.types.ts index 84e843fada277..94f61e8b633c6 100644 --- a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.types.ts +++ b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.types.ts @@ -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 } @@ -29,10 +14,6 @@ export type PageParam = { cursor: number; direction: 'next' | 'prev' } | undefin export type SearchParamsType = inferParserType export type QuerySearchParamsType = Omit -export type SearchParams = { - [key: string]: string | string[] | undefined -} - /** ----------------------------------------- */ export type SheetField> = { diff --git a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.utils.ts b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.utils.ts index 9247288a343ef..c526a0cfcdd45 100644 --- a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.utils.ts +++ b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.utils.ts @@ -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 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 = (facets?: Record) => { return (_table: TTable, columnId: string) => { return new Map(facets?.[columnId]?.rows?.map(({ value, total }) => [value, total]) || []) diff --git a/apps/studio/components/interfaces/UnifiedLogs/components/DataTableSheetContent.tsx b/apps/studio/components/interfaces/UnifiedLogs/components/DataTableSheetContent.tsx deleted file mode 100644 index 6d80888fbef5b..0000000000000 --- a/apps/studio/components/interfaces/UnifiedLogs/components/DataTableSheetContent.tsx +++ /dev/null @@ -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 { - fields: SheetField[] -} - -const SheetDetailsContentSkeleton = ({ - fields, -}: SheetDetailsContentSkeletonProps) => { - return ( -
- {fields.map((field) => ( -
-
{field.label}
-
- -
-
- ))} -
- ) -} - -interface DataTableSheetContentProps extends HTMLAttributes { - data?: TData - table: Table - fields: SheetField[] - filterFields: DataTableFilterField[] - metadata?: TMeta & { - totalRows: number - filterRows: number - totalRowsFetched: number - } -} - -export function DataTableSheetContent({ - data, - table, - className, - fields, - filterFields, - metadata, - ...props -}: DataTableSheetContentProps) { - if (!data) return - - return ( -
- {fields.map((field) => { - if (field.condition && !field.condition(data)) return null - - const Component = field.component - const value = String(data[field.id]) - - return ( -
- {field.type === 'readonly' ? ( -
-
{field.label}
-
- {Component ? : value} -
-
- ) : ( - -
{field.label}
-
- {Component ? : value} -
-
- )} -
- ) - })} -
- ) -} - -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 diff --git a/apps/studio/components/interfaces/UnifiedLogs/components/LogTypeIcon.tsx b/apps/studio/components/interfaces/UnifiedLogs/components/LogTypeIcon.tsx index 298a048b7f874..2d85e47a3aa02 100644 --- a/apps/studio/components/interfaces/UnifiedLogs/components/LogTypeIcon.tsx +++ b/apps/studio/components/interfaces/UnifiedLogs/components/LogTypeIcon.tsx @@ -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 = ({ ) } - -export const LogTypeIconWithText = ({ - type, - size = 16, - strokeWidth = 1.5, - className, -}: LogTypeIconProps) => { - return ( -
- - {type} -
- ) -}