diff --git a/apps/studio/components/interfaces/Settings/Logs/LogEventChart.tsx b/apps/studio/components/interfaces/Settings/Logs/LogEventChart.tsx index d403fda3a5b83..a6ae2eab3d3a2 100644 --- a/apps/studio/components/interfaces/Settings/Logs/LogEventChart.tsx +++ b/apps/studio/components/interfaces/Settings/Logs/LogEventChart.tsx @@ -4,22 +4,25 @@ import type { EventChartData } from './Logs.types' export interface LogEventChartProps { data: EventChartData[] - onBarClick: (isoTimestamp: string) => void + onBarClick: (datum: Datum) => void } -const LogEventChart = ({ data, onBarClick }: LogEventChartProps) => ( - { - if (!datum.timestamp) return - onBarClick(datum.timestamp as string) - }} - customDateFormat="MMM D, HH:mm:s" - /> -) +const LogEventChart = ({ data, onBarClick }: LogEventChartProps) => { + return ( + { + if (!datum?.timestamp) return + onBarClick(datum) + }} + customDateFormat="MMM D, HH:mm:s" + xAxisIsDate={true} + /> + ) +} export default LogEventChart diff --git a/apps/studio/components/interfaces/Settings/Logs/Logs.types.ts b/apps/studio/components/interfaces/Settings/Logs/Logs.types.ts index f9c055618af46..ff6bfac43a53d 100644 --- a/apps/studio/components/interfaces/Settings/Logs/Logs.types.ts +++ b/apps/studio/components/interfaces/Settings/Logs/Logs.types.ts @@ -53,6 +53,7 @@ export interface CountData { export interface EventChartData extends Datum { count: number timestamp: string + has_error: boolean } type LFResponse = { diff --git a/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts b/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts index 3410dc3da7b60..e336e62dba26e 100644 --- a/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts +++ b/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts @@ -296,6 +296,24 @@ const calcChartStart = (params: Partial): [Dayjs, string] => return [its.add(-extendValue, trunc), trunc] } +function getErrorCondition(table: LogsTableName): string { + switch (table) { + case 'edge_logs': + return 'response.status_code >= 400' + case 'postgres_logs': + return "parsed.error_severity IN ('ERROR', 'FATAL', 'PANIC')" + case 'function_logs': + return "metadata.level IN ('error', 'fatal')" + case 'auth_logs': + return "metadata.level = 'error' OR metadata.status >= 400" + case 'function_edge_logs': + return 'response.status_code >= 400' + // Add conditions for other log types as needed + default: + return 'false' // Default to no errors if table type is unknown + } +} + /** * * generates log event chart query @@ -308,13 +326,16 @@ export const genChartQuery = ( const [startOffset, trunc] = calcChartStart(params) const where = genWhereStatement(table, filters) + const errorCondition = getErrorCondition(table) + let joins = genCrossJoinUnnests(table) return ` SELECT -- log-event-chart timestamp_trunc(t.timestamp, ${trunc}) as timestamp, - count(t.timestamp) as count + count(t.timestamp) as count, + LOGICAL_OR(${errorCondition}) as has_error FROM ${table} t ${joins} diff --git a/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx b/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx index 22d16b4419941..682e5aaa5ccee 100644 --- a/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx +++ b/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx @@ -19,6 +19,7 @@ import { LOGS_TABLES, LOG_ROUTES_WITH_REPLICA_SUPPORT, LogsTableName } from './L import type { Filters, LogSearchCallback, LogTemplate, QueryType } from './Logs.types' import { ensureNoTimestampConflict, maybeShowUpgradePrompt } from './Logs.utils' import UpgradePrompt from './UpgradePrompt' +import dayjs from 'dayjs' /** * Acts as a container component for the entire log display @@ -228,11 +229,20 @@ export const LogsPreviewer = ({ {!isLoading && showChart && ( { + onBarClick={({ isoTimestamp }) => { + // from should be $RANGE minutes before the bar + // to should be $RANGE minutes after the bar + const RANGE = 5 + const from = dayjs(isoTimestamp as string) + .subtract(RANGE, 'minute') + .toISOString() + const to = dayjs(isoTimestamp as string) + .add(RANGE, 'minute') + .toISOString() handleSearch('event-chart-bar-click', { query: filters.search_query as string, - to: isoTimestamp as string, - from: null, + to: to, + from: from, }) }} /> diff --git a/apps/studio/components/ui/Charts/AreaChart.tsx b/apps/studio/components/ui/Charts/AreaChart.tsx index 933999a0cf198..09861218cdaf7 100644 --- a/apps/studio/components/ui/Charts/AreaChart.tsx +++ b/apps/studio/components/ui/Charts/AreaChart.tsx @@ -39,7 +39,7 @@ const AreaChart = ({ (focusDataIndex !== null && data && data[focusDataIndex] !== undefined && - day(data[focusDataIndex][xAxisKey]).format(customDateFormat)) || + day(String(data[focusDataIndex][xAxisKey])).format(customDateFormat)) || highlightedLabel const resolvedHighlightedValue = @@ -66,9 +66,11 @@ const AreaChart = ({ highlightedValue={ typeof resolvedHighlightedValue === 'number' ? numberFormatter(resolvedHighlightedValue, valuePrecision) - : resolvedHighlightedValue + : typeof resolvedHighlightedValue === 'string' + ? resolvedHighlightedValue + : '' } - highlightedLabel={resolvedHighlightedLabel} + highlightedLabel={String(resolvedHighlightedLabel || '')} minimalHeader={minimalHeader} /> @@ -117,8 +119,8 @@ const AreaChart = ({ {data && (
- {dayjs(data[0][xAxisKey]).format(customDateFormat)} - {dayjs(data[data?.length - 1]?.[xAxisKey]).format(customDateFormat)} + {dayjs(String(data[0][xAxisKey])).format(customDateFormat)} + {dayjs(String(data[data?.length - 1]?.[xAxisKey])).format(customDateFormat)}
)} diff --git a/apps/studio/components/ui/Charts/BarChart.tsx b/apps/studio/components/ui/Charts/BarChart.tsx index ea05d94ae0ecf..69faf23108315 100644 --- a/apps/studio/components/ui/Charts/BarChart.tsx +++ b/apps/studio/components/ui/Charts/BarChart.tsx @@ -82,7 +82,7 @@ const BarChart = ({ (focusDataIndex !== null && data && data[focusDataIndex] !== undefined && - day(data[focusDataIndex][xAxisKey]).format(customDateFormat)) || + day(String(data[focusDataIndex][xAxisKey])).format(customDateFormat)) || highlightedLabel ) } @@ -114,9 +114,11 @@ const BarChart = ({ highlightedValue={ typeof resolvedHighlightedValue === 'number' ? numberFormatter(resolvedHighlightedValue, valuePrecision) - : resolvedHighlightedValue + : typeof resolvedHighlightedValue === 'string' + ? resolvedHighlightedValue + : '' } - highlightedLabel={resolvedHighlightedLabel} + highlightedLabel={String(resolvedHighlightedLabel || '')} minimalHeader={minimalHeader} /> @@ -163,9 +165,11 @@ const BarChart = ({ key={`cell-${index}`} className={`transition-all duration-300 ${onBarClick ? 'cursor-pointer' : ''}`} fill={ - focusDataIndex === index || focusDataIndex === null - ? CHART_COLORS.GREEN_1 - : CHART_COLORS.GREEN_2 + _entry.has_error + ? CHART_COLORS.YELLOW_1 + : focusDataIndex === index || focusDataIndex === null + ? CHART_COLORS.GREEN_1 + : CHART_COLORS.GREEN_2 } enableBackground={12} /> @@ -176,11 +180,13 @@ const BarChart = ({ {data && (
- {xAxisIsDate ? day(data[0][xAxisKey]).format(customDateFormat) : data[0][xAxisKey]} + {xAxisIsDate + ? day(String(data[0][xAxisKey])).format(customDateFormat) + : data[0][xAxisKey]} {xAxisIsDate - ? day(data[data?.length - 1]?.[xAxisKey]).format(customDateFormat) + ? day(String(data[data?.length - 1]?.[xAxisKey])).format(customDateFormat) : data[data?.length - 1]?.[xAxisKey]}
diff --git a/apps/studio/components/ui/Charts/Charts.constants.ts b/apps/studio/components/ui/Charts/Charts.constants.ts index a8bb2e8aca530..671c5e2d70fde 100644 --- a/apps/studio/components/ui/Charts/Charts.constants.ts +++ b/apps/studio/components/ui/Charts/Charts.constants.ts @@ -7,6 +7,7 @@ export const CHART_COLORS = { AXIS: 'hsl(var(--background-overlay-hover))', GREEN_1: 'hsl(var(--brand-default))', // #3ECF8E GREEN_2: 'hsl(var(--brand-500))', + YELLOW_1: 'hsl(var(--warning-600))', } // refer to packages/ui/radix-colors.js for full list of colors diff --git a/apps/studio/components/ui/Charts/Charts.types.tsx b/apps/studio/components/ui/Charts/Charts.types.tsx index 2890e58f4440e..b836c0973ca3f 100644 --- a/apps/studio/components/ui/Charts/Charts.types.tsx +++ b/apps/studio/components/ui/Charts/Charts.types.tsx @@ -38,7 +38,7 @@ export type HeaderType = { displayDateInUtc?: boolean } -export type Datum = Record +export type Datum = Record export interface TimeseriesDatum extends Datum { timestamp: string diff --git a/apps/studio/pages/project/[ref]/logs/edge-logs.tsx b/apps/studio/pages/project/[ref]/logs/edge-logs.tsx index 923dca7a204a8..dcef42de7c791 100644 --- a/apps/studio/pages/project/[ref]/logs/edge-logs.tsx +++ b/apps/studio/pages/project/[ref]/logs/edge-logs.tsx @@ -19,6 +19,6 @@ export const LogPage: NextPageWithLayout = () => { ) } -LogPage.getLayout = (page) => {page} +LogPage.getLayout = (page) => {page} export default LogPage diff --git a/apps/studio/tests/pages/projects/LogEventChart.test.tsx b/apps/studio/tests/pages/projects/LogEventChart.test.tsx index bc488e2c289e7..b9f265c28ba1f 100644 --- a/apps/studio/tests/pages/projects/LogEventChart.test.tsx +++ b/apps/studio/tests/pages/projects/LogEventChart.test.tsx @@ -25,8 +25,8 @@ test('renders chart', async () => { render(