Files
supabase/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionInvocationsSection.tsx
T
Saxon FletcherandAli Waseem e21088144d Edge function overview (#44009)
<img width="1575" height="1134" alt="image"
src="https://github.com/user-attachments/assets/994b1113-717f-44a2-89a4-13bc0182db20"
/>

Attempts to improve our edge function overview pages to provide stronger
insights into the health of a function, including reliability (error
rates), performance (execution times) and usage (cpu and memory).

As part of this work it refactors existing charts to use our new chart
components.

main consideration is the collective performance of error queries
https://github.com/supabase/supabase/pull/44009/changes#diff-2a79cf61c5397a8ef363c333229fa7729a2efc90a4d8e0806e49c212d5aa97e7

## To test:
1. Create an edge function that errors out randomly across requests. You
can use cron to poll this function every second.
2. View the edge function and on the overview page confirm that errors
are showing and grouped correctly in recent failed invocations sections.

---------

Co-authored-by: Ali Waseem <waseema393@gmail.com>
2026-04-01 14:59:12 +10:00

157 lines
5.4 KiB
TypeScript

import AlertError from 'components/ui/AlertError'
import type { ComponentProps } from 'react'
import { useMemo } from 'react'
import type { ChartIntervals } from 'types'
import { Button } from 'ui'
import { Chart, ChartActions, ChartLoadingState, ChartMetric } from 'ui-patterns/Chart'
import { PageContainer } from 'ui-patterns/PageContainer'
import {
PageSection,
PageSectionAside,
PageSectionContent,
PageSectionMeta,
PageSectionSummary,
} from 'ui-patterns/PageSection'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { EdgeFunctionChartEmptyState } from './EdgeFunctionChartEmptyState'
import { EdgeFunctionInvocationsChart } from './EdgeFunctionInvocationsChart'
import {
EDGE_FUNCTION_CHART_INTERVALS,
formatRate,
getChartEmptyStateCopy,
getSegmentedButtonClassName,
} from './EdgeFunctionOverview.utils'
import { toAlertError } from './EdgeFunctionRecentErrors.utils'
import type { InvocationChartDatum, InvocationUpdateAnnotation } from './EdgeFunctionOverview.utils'
interface EdgeFunctionInvocationsSectionProps {
interval: string
onIntervalChange: (interval: string) => void
selectedInterval: ChartIntervals
actions?: ComponentProps<typeof ChartActions>['actions']
totalInvocationCount: number
totalErrorCount: number
totalWarningCount: number
isLoadingFunction: boolean
isErrorFunction: boolean
functionError?: unknown
isLoadingChart: boolean
isErrorChart: boolean
chartErrorMessage?: string
chartData: InvocationChartDatum[]
onChartClick: () => void
updateAnnotation?: InvocationUpdateAnnotation
}
export const EdgeFunctionInvocationsSection = ({
interval,
onIntervalChange,
selectedInterval,
actions,
totalInvocationCount,
totalErrorCount,
totalWarningCount,
isLoadingFunction,
isErrorFunction,
functionError,
isLoadingChart,
isErrorChart,
chartErrorMessage,
chartData,
onChartClick,
updateAnnotation,
}: EdgeFunctionInvocationsSectionProps) => {
const dateTimeFormat = selectedInterval.format ?? 'MMM D, h:mma'
const emptyStateCopy = useMemo(
() => getChartEmptyStateCopy('invocations', isErrorChart, chartErrorMessage),
[chartErrorMessage, isErrorChart]
)
return (
<PageSection className="bg-surface-100/50 border-b pb-10 pt-8">
<PageSectionContent>
<PageContainer size="full">
<div className="flex flex-col gap-5">
<PageSectionMeta className="!items-center">
<PageSectionSummary>
<div className="flex flex-wrap items-start gap-x-8 gap-y-4">
<ChartMetric
label="Total Invocations"
value={totalInvocationCount}
status="default"
tooltip="Total number of invocations"
/>
<ChartMetric
label="5xx Rate"
value={formatRate(totalErrorCount, totalInvocationCount)}
status="negative"
tooltip="Share of invocations that returned a 5xx status code"
/>
<ChartMetric
label="4xx Rate"
value={formatRate(totalWarningCount, totalInvocationCount)}
status="warning"
tooltip="Share of invocations that returned a 4xx status code"
/>
</div>
</PageSectionSummary>
<PageSectionAside className="flex-wrap @xl:self-center">
<div className="flex items-center">
{EDGE_FUNCTION_CHART_INTERVALS.map((item, index) => {
return (
<Button
key={`function-filter-${item.key}`}
type={interval === item.key ? 'secondary' : 'default'}
onClick={() => onIntervalChange(item.key)}
className={getSegmentedButtonClassName(
index,
EDGE_FUNCTION_CHART_INTERVALS.length
)}
>
{item.label}
</Button>
)
})}
</div>
<ChartActions actions={actions} />
</PageSectionAside>
</PageSectionMeta>
<div>
{isLoadingFunction && <GenericSkeletonLoader />}
{isErrorFunction && (
<AlertError
error={toAlertError(functionError)}
subject="Failed to retrieve edge function details"
layout="vertical"
/>
)}
</div>
<div>
<Chart isLoading={isLoadingChart}>
{isLoadingChart ? (
<ChartLoadingState />
) : isErrorChart || chartData.length === 0 ? (
<EdgeFunctionChartEmptyState
title={emptyStateCopy.title}
description={emptyStateCopy.description}
/>
) : (
<EdgeFunctionInvocationsChart
chartData={chartData}
dateTimeFormat={dateTimeFormat}
onChartClick={onChartClick}
updateAnnotation={updateAnnotation}
/>
)}
</Chart>
</div>
</div>
</PageContainer>
</PageSectionContent>
</PageSection>
)
}