mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Surface error from fillTimeseries to the UI (#28086)
* Surface error from fillTimeseries to the UI * rm consolelog --------- Co-authored-by: Jordi Enric <37541088+jordienr@users.noreply.github.com>
This commit is contained in:
1 parent
9081324666
commit
91c63be9c9
6 files changed
+170
-72
No files matched your search
@@ -9,7 +9,7 @@ import { useParams } from 'common'
|
||||
import BarChart from 'components/ui/Charts/BarChart'
|
||||
import Panel from 'components/ui/Panel'
|
||||
import { UsageApiCounts, useProjectLogStatsQuery } from 'data/analytics/project-log-stats-query'
|
||||
import useFillTimeseriesSorted from 'hooks/analytics/useFillTimeseriesSorted'
|
||||
import { useFillTimeseriesSorted } from 'hooks/analytics/useFillTimeseriesSorted'
|
||||
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
|
||||
import type { ChartIntervals } from 'types'
|
||||
import {
|
||||
@@ -53,7 +53,7 @@ const ProjectUsage = () => {
|
||||
selectedInterval.startUnit as dayjs.ManipulateType
|
||||
)
|
||||
const endDateLocal = dayjs()
|
||||
const charts = useFillTimeseriesSorted(
|
||||
const { data: charts } = useFillTimeseriesSorted(
|
||||
data?.result || [],
|
||||
'timestamp',
|
||||
[
|
||||
|
||||
@@ -9,12 +9,19 @@ import {
|
||||
} from 'components/interfaces/Settings/Logs/LogsFormatters'
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import BarChart from 'components/ui/Charts/BarChart'
|
||||
import useFillTimeseriesSorted from 'hooks/analytics/useFillTimeseriesSorted'
|
||||
import { Button, Collapsible } from 'ui'
|
||||
import { useFillTimeseriesSorted } from 'hooks/analytics/useFillTimeseriesSorted'
|
||||
import {
|
||||
Alert_Shadcn_,
|
||||
AlertDescription_Shadcn_,
|
||||
AlertTitle_Shadcn_,
|
||||
Button,
|
||||
Collapsible,
|
||||
} from 'ui'
|
||||
import { queryParamsToObject } from '../Reports.utils'
|
||||
import { ReportWidgetProps, ReportWidgetRendererProps } from '../ReportWidget'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import { ResponseError } from 'types'
|
||||
import { WarningIcon } from 'ui-patterns/Icons/StatusIcons'
|
||||
|
||||
export const NetworkTrafficRenderer = (
|
||||
props: ReportWidgetProps<{
|
||||
@@ -23,7 +30,7 @@ export const NetworkTrafficRenderer = (
|
||||
egress: number
|
||||
}>
|
||||
) => {
|
||||
const data = useFillTimeseriesSorted(
|
||||
const { data, error, isError } = useFillTimeseriesSorted(
|
||||
props.data,
|
||||
'timestamp',
|
||||
['ingress_mb', 'egress_mb'],
|
||||
@@ -31,6 +38,7 @@ export const NetworkTrafficRenderer = (
|
||||
props.params?.iso_timestamp_start,
|
||||
props.params?.iso_timestamp_end
|
||||
)
|
||||
|
||||
const totalIngress = sumBy(props.data, 'ingress_mb')
|
||||
const totalEgress = sumBy(props.data, 'egress_mb')
|
||||
|
||||
@@ -38,11 +46,19 @@ export const NetworkTrafficRenderer = (
|
||||
return valueInMb < 0.001 ? 7 : totalIngress > 1 ? 2 : 4
|
||||
}
|
||||
|
||||
if (props.error !== null && props.error !== undefined) {
|
||||
if (!!props.error) {
|
||||
const error = (
|
||||
typeof props.error === 'string' ? { message: props.error } : props.error
|
||||
) as ResponseError
|
||||
return <AlertError subject="Failed to retrieve network traffic" error={error} />
|
||||
} else if (isError) {
|
||||
return (
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>Failed to retrieve network traffic</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>{error.message}</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -85,7 +101,7 @@ export const TotalRequestsChartRenderer = (
|
||||
const total = props.data.reduce((acc, datum) => {
|
||||
return acc + datum.count
|
||||
}, 0)
|
||||
const data = useFillTimeseriesSorted(
|
||||
const { data, error, isError } = useFillTimeseriesSorted(
|
||||
props.data,
|
||||
'timestamp',
|
||||
'count',
|
||||
@@ -94,11 +110,19 @@ export const TotalRequestsChartRenderer = (
|
||||
props.params?.iso_timestamp_end
|
||||
)
|
||||
|
||||
if (props.error !== null && props.error !== undefined) {
|
||||
if (!!props.error) {
|
||||
const error = (
|
||||
typeof props.error === 'string' ? { message: props.error } : props.error
|
||||
) as ResponseError
|
||||
return <AlertError subject="Failed to retrieve total requests" error={error} />
|
||||
} else if (isError) {
|
||||
return (
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>Failed to retrieve total requests</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>{error.message}</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -225,7 +249,7 @@ export const ErrorCountsChartRenderer = (
|
||||
return acc + datum.count
|
||||
}, 0)
|
||||
|
||||
const data = useFillTimeseriesSorted(
|
||||
const { data, error, isError } = useFillTimeseriesSorted(
|
||||
props.data,
|
||||
'timestamp',
|
||||
'count',
|
||||
@@ -234,11 +258,19 @@ export const ErrorCountsChartRenderer = (
|
||||
props.params?.iso_timestamp_end
|
||||
)
|
||||
|
||||
if (props.error !== null && props.error !== undefined) {
|
||||
if (!!props.error) {
|
||||
const error = (
|
||||
typeof props.error === 'string' ? { message: props.error } : props.error
|
||||
) as ResponseError
|
||||
return <AlertError subject="Failed to retrieve request errors" error={error} />
|
||||
} else if (isError) {
|
||||
return (
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>Failed to retrieve request errors</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>{error.message}</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -266,7 +298,7 @@ export const ResponseSpeedChartRenderer = (
|
||||
avg: datum.avg,
|
||||
}))
|
||||
|
||||
const data = useFillTimeseriesSorted(
|
||||
const { data, error, isError } = useFillTimeseriesSorted(
|
||||
transformedData,
|
||||
'timestamp',
|
||||
'avg',
|
||||
@@ -277,11 +309,19 @@ export const ResponseSpeedChartRenderer = (
|
||||
|
||||
const lastAvg = props.data[props.data.length - 1]?.avg
|
||||
|
||||
if (props.error !== null && props.error !== undefined) {
|
||||
if (!!props.error) {
|
||||
const error = (
|
||||
typeof props.error === 'string' ? { message: props.error } : props.error
|
||||
) as ResponseError
|
||||
return <AlertError subject="Failed to retrieve response speeds" error={error} />
|
||||
} else if (isError) {
|
||||
return (
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>Failed to retrieve response speeds</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>{error.message}</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -466,7 +466,7 @@ export const fillTimeseries = (
|
||||
// Intentional throwing of error here to be caught by Sentry, as this would indicate a bug since charts shouldn't be rendering more than 10k data points
|
||||
if (diff > 10000) {
|
||||
throw new Error(
|
||||
'Data error, filling timeseries dynamically with more than 10k data points degrades performance.'
|
||||
'The selected date range will render more than 10,000 data points within the charts, which will degrade browser performance. Please select a smaller date range.'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,15 +4,32 @@ import { useMemo } from 'react'
|
||||
/**
|
||||
* Convenience hook for memoized filling of timeseries data.
|
||||
*/
|
||||
const useFillTimeseriesSorted = (...args: Parameters<typeof fillTimeseries>) => {
|
||||
export const useFillTimeseriesSorted = (...args: Parameters<typeof fillTimeseries>) => {
|
||||
return useMemo(() => {
|
||||
const [data, timestampKey] = args
|
||||
if (!data[0]?.[timestampKey]) return data
|
||||
if (!data[0]?.[timestampKey])
|
||||
return {
|
||||
data,
|
||||
error: undefined,
|
||||
isError: false,
|
||||
}
|
||||
|
||||
const filled = fillTimeseries(...args)
|
||||
return filled.sort((a, b) => {
|
||||
return (new Date(a[args[1]]) as any) - (new Date(b[args[1]]) as any)
|
||||
})
|
||||
try {
|
||||
const filled = fillTimeseries(...args)
|
||||
|
||||
return {
|
||||
data: filled.sort((a, b) => {
|
||||
return (new Date(a[args[1]]) as any) - (new Date(b[args[1]]) as any)
|
||||
}),
|
||||
error: undefined,
|
||||
isError: false,
|
||||
}
|
||||
} catch (error: any) {
|
||||
return {
|
||||
data: [],
|
||||
error,
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
}, [JSON.stringify(args[0]), ...args])
|
||||
}
|
||||
export default useFillTimeseriesSorted
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from 'components/interfaces/Settings/Logs/Logs.utils'
|
||||
import { get, isResponseOk } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import useFillTimeseriesSorted from './useFillTimeseriesSorted'
|
||||
import { useFillTimeseriesSorted } from './useFillTimeseriesSorted'
|
||||
import useTimeseriesUnixToIso from './useTimeseriesUnixToIso'
|
||||
|
||||
interface LogsPreviewHook {
|
||||
@@ -194,7 +194,7 @@ function useLogsPreview(
|
||||
'timestamp'
|
||||
)
|
||||
|
||||
const eventChartData = useFillTimeseriesSorted(
|
||||
const { data: eventChartData, error: eventChartError } = useFillTimeseriesSorted(
|
||||
normalizedEventChartData,
|
||||
'timestamp',
|
||||
'count',
|
||||
@@ -209,7 +209,7 @@ function useLogsPreview(
|
||||
logData,
|
||||
isLoading: isLoading || isRefetching,
|
||||
isLoadingOlder: isFetchingNextPage,
|
||||
error,
|
||||
error: error || eventChartError,
|
||||
filters,
|
||||
params,
|
||||
oldestTimestamp: oldestTimestamp ? String(oldestTimestamp) : undefined,
|
||||
|
||||
@@ -14,10 +14,11 @@ import NoPermission from 'components/ui/NoPermission'
|
||||
import { useFunctionsReqStatsQuery } from 'data/analytics/functions-req-stats-query'
|
||||
import { useFunctionsResourceUsageQuery } from 'data/analytics/functions-resource-usage-query'
|
||||
import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query'
|
||||
import useFillTimeseriesSorted from 'hooks/analytics/useFillTimeseriesSorted'
|
||||
import { useFillTimeseriesSorted } from 'hooks/analytics/useFillTimeseriesSorted'
|
||||
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import type { ChartIntervals, NextPageWithLayout } from 'types'
|
||||
import { Button } from 'ui'
|
||||
import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button } from 'ui'
|
||||
import { WarningIcon } from 'ui-patterns/Icons/StatusIcons'
|
||||
|
||||
const CHART_INTERVALS: ChartIntervals[] = [
|
||||
{
|
||||
@@ -98,7 +99,11 @@ const PageLayout: NextPageWithLayout = () => {
|
||||
return [start, end]
|
||||
}, [selectedInterval])
|
||||
|
||||
const execTimeChartData = useFillTimeseriesSorted(
|
||||
const {
|
||||
data: execTimeChartData,
|
||||
error: execTimeError,
|
||||
isError: isErrorExecTime,
|
||||
} = useFillTimeseriesSorted(
|
||||
reqStatsData,
|
||||
'timestamp',
|
||||
['avg_execution_time'],
|
||||
@@ -107,7 +112,11 @@ const PageLayout: NextPageWithLayout = () => {
|
||||
endDate.toISOString()
|
||||
)
|
||||
|
||||
const invocationsChartData = useFillTimeseriesSorted(
|
||||
const {
|
||||
data: invocationsChartData,
|
||||
error: invocationsError,
|
||||
isError: isErrorInvocations,
|
||||
} = useFillTimeseriesSorted(
|
||||
reqStatsData,
|
||||
'timestamp',
|
||||
['count', 'success_count', 'redirect_count', 'client_err_count', 'server_err_count'],
|
||||
@@ -116,7 +125,11 @@ const PageLayout: NextPageWithLayout = () => {
|
||||
endDate.toISOString()
|
||||
)
|
||||
|
||||
const resourceUsageChartData = useFillTimeseriesSorted(
|
||||
const {
|
||||
data: resourceUsageChartData,
|
||||
error: resourceUsageError,
|
||||
isError: isErrorResourceUsage,
|
||||
} = useFillTimeseriesSorted(
|
||||
resourceUsageData,
|
||||
'timestamp',
|
||||
['avg_cpu_time_used', 'avg_memory_used'],
|
||||
@@ -172,7 +185,13 @@ const PageLayout: NextPageWithLayout = () => {
|
||||
data={execTimeChartData}
|
||||
isLoading={reqStatsResult.isLoading}
|
||||
renderer={(props) => {
|
||||
return (
|
||||
return isErrorExecTime ? (
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>Failed to reterieve execution time</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>{execTimeError.message}</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
) : (
|
||||
<AreaChart
|
||||
className="w-full"
|
||||
xAxisKey="timestamp"
|
||||
@@ -190,48 +209,58 @@ const PageLayout: NextPageWithLayout = () => {
|
||||
data={invocationsChartData}
|
||||
isLoading={reqStatsResult.isLoading}
|
||||
renderer={(props) => {
|
||||
const data = props.data
|
||||
.map((d: any) => [
|
||||
{
|
||||
status: '2xx',
|
||||
count: d.success_count,
|
||||
timestamp: d.timestamp,
|
||||
},
|
||||
{
|
||||
status: '3xx',
|
||||
count: d.redirect_count,
|
||||
timestamp: d.timestamp,
|
||||
},
|
||||
{
|
||||
status: '4xx',
|
||||
count: d.client_err_count,
|
||||
timestamp: d.timestamp,
|
||||
},
|
||||
{
|
||||
status: '5xx',
|
||||
count: d.server_err_count,
|
||||
timestamp: d.timestamp,
|
||||
},
|
||||
])
|
||||
.flat()
|
||||
if (isErrorInvocations) {
|
||||
return (
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>Failed to reterieve invocations</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>{invocationsError.message}</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)
|
||||
} else {
|
||||
const data = props.data
|
||||
.map((d: any) => [
|
||||
{
|
||||
status: '2xx',
|
||||
count: d.success_count,
|
||||
timestamp: d.timestamp,
|
||||
},
|
||||
{
|
||||
status: '3xx',
|
||||
count: d.redirect_count,
|
||||
timestamp: d.timestamp,
|
||||
},
|
||||
{
|
||||
status: '4xx',
|
||||
count: d.client_err_count,
|
||||
timestamp: d.timestamp,
|
||||
},
|
||||
{
|
||||
status: '5xx',
|
||||
count: d.server_err_count,
|
||||
timestamp: d.timestamp,
|
||||
},
|
||||
])
|
||||
.flat()
|
||||
|
||||
return (
|
||||
<StackedBarChart
|
||||
className="w-full"
|
||||
xAxisKey="timestamp"
|
||||
yAxisKey="count"
|
||||
stackKey="status"
|
||||
data={data}
|
||||
highlightedValue={sumBy(data, 'count')}
|
||||
customDateFormat={selectedInterval.format}
|
||||
stackColors={['brand', 'slate', 'yellow', 'red']}
|
||||
onBarClick={() => {
|
||||
router.push(
|
||||
`/project/${projectRef}/functions/${functionSlug}/invocations?its=${startDate.toISOString()}`
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<StackedBarChart
|
||||
className="w-full"
|
||||
xAxisKey="timestamp"
|
||||
yAxisKey="count"
|
||||
stackKey="status"
|
||||
data={data}
|
||||
highlightedValue={sumBy(data, 'count')}
|
||||
customDateFormat={selectedInterval.format}
|
||||
stackColors={['brand', 'slate', 'yellow', 'red']}
|
||||
onBarClick={() => {
|
||||
router.push(
|
||||
`/project/${projectRef}/functions/${functionSlug}/invocations?its=${startDate.toISOString()}`
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ReportWidget
|
||||
@@ -240,7 +269,13 @@ const PageLayout: NextPageWithLayout = () => {
|
||||
data={resourceUsageChartData}
|
||||
isLoading={resourceUsageResult.isLoading}
|
||||
renderer={(props) => {
|
||||
return (
|
||||
return isErrorResourceUsage ? (
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>Failed to retrieve CPU time</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>{resourceUsageError.message}</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
) : (
|
||||
<AreaChart
|
||||
className="w-full"
|
||||
xAxisKey="timestamp"
|
||||
@@ -259,7 +294,13 @@ const PageLayout: NextPageWithLayout = () => {
|
||||
data={resourceUsageChartData}
|
||||
isLoading={resourceUsageResult.isLoading}
|
||||
renderer={(props) => {
|
||||
return (
|
||||
return isErrorResourceUsage ? (
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>Failed to retrieve memory usage</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>{resourceUsageError.message}</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
) : (
|
||||
<AreaChart
|
||||
className="w-full"
|
||||
xAxisKey="timestamp"
|
||||
|
||||
Reference in new issue
Block a user