diff --git a/apps/studio/components/interfaces/Home/ProjectUsage.tsx b/apps/studio/components/interfaces/Home/ProjectUsage.tsx
index ed2efedecb6..b872fc4657c 100644
--- a/apps/studio/components/interfaces/Home/ProjectUsage.tsx
+++ b/apps/studio/components/interfaces/Home/ProjectUsage.tsx
@@ -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',
[
diff --git a/apps/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx b/apps/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx
index a666c19fbd4..18f848a3938 100644
--- a/apps/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx
+++ b/apps/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx
@@ -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
+ } else if (isError) {
+ return (
+
+
+ Failed to retrieve network traffic
+ {error.message}
+
+ )
}
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
+ } else if (isError) {
+ return (
+
+
+ Failed to retrieve total requests
+ {error.message}
+
+ )
}
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
+ } else if (isError) {
+ return (
+
+
+ Failed to retrieve request errors
+ {error.message}
+
+ )
}
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
+ } else if (isError) {
+ return (
+
+
+ Failed to retrieve response speeds
+ {error.message}
+
+ )
}
return (
diff --git a/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts b/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts
index f7929ab01d7..3410dc3da7b 100644
--- a/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts
+++ b/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts
@@ -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.'
)
}
diff --git a/apps/studio/hooks/analytics/useFillTimeseriesSorted.ts b/apps/studio/hooks/analytics/useFillTimeseriesSorted.ts
index 1af62443c46..af81240c765 100644
--- a/apps/studio/hooks/analytics/useFillTimeseriesSorted.ts
+++ b/apps/studio/hooks/analytics/useFillTimeseriesSorted.ts
@@ -4,15 +4,32 @@ import { useMemo } from 'react'
/**
* Convenience hook for memoized filling of timeseries data.
*/
-const useFillTimeseriesSorted = (...args: Parameters) => {
+export const useFillTimeseriesSorted = (...args: Parameters) => {
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
diff --git a/apps/studio/hooks/analytics/useLogsPreview.tsx b/apps/studio/hooks/analytics/useLogsPreview.tsx
index fb1236358e9..30b14092d2d 100644
--- a/apps/studio/hooks/analytics/useLogsPreview.tsx
+++ b/apps/studio/hooks/analytics/useLogsPreview.tsx
@@ -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,
diff --git a/apps/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx b/apps/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx
index 4c3631dc4ad..3f57756e7a8 100644
--- a/apps/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx
+++ b/apps/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx
@@ -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 ? (
+
+
+ Failed to reterieve execution time
+ {execTimeError.message}
+
+ ) : (
{
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 (
+
+
+ Failed to reterieve invocations
+ {invocationsError.message}
+
+ )
+ } 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 (
- {
- router.push(
- `/project/${projectRef}/functions/${functionSlug}/invocations?its=${startDate.toISOString()}`
- )
- }}
- />
- )
+ return (
+ {
+ router.push(
+ `/project/${projectRef}/functions/${functionSlug}/invocations?its=${startDate.toISOString()}`
+ )
+ }}
+ />
+ )
+ }
}}
/>
{
data={resourceUsageChartData}
isLoading={resourceUsageResult.isLoading}
renderer={(props) => {
- return (
+ return isErrorResourceUsage ? (
+
+
+ Failed to retrieve CPU time
+ {resourceUsageError.message}
+
+ ) : (
{
data={resourceUsageChartData}
isLoading={resourceUsageResult.isLoading}
renderer={(props) => {
- return (
+ return isErrorResourceUsage ? (
+
+
+ Failed to retrieve memory usage
+ {resourceUsageError.message}
+
+ ) : (