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:
Joshen LimandJordi Enric authored and GitHub committed 2024-07-20 00:47:23 +08:00
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"