From 3f6cd1f18827a3271b38b96c6154010b26fbd1bc Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Wed, 25 Mar 2026 19:23:46 +1000 Subject: [PATCH] error rates in functions list (#44006) image Adds total request and error rate columns to edge functions table behind a feature flag --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: SaxonF <1072756+SaxonF@users.noreply.github.com> Co-authored-by: Joshen Lim --- .../Functions/EdgeFunctionsListItem.tsx | 83 ++++++++++-- .../EdgeFunctionsListItem.utils.test.ts | 25 ++++ .../Functions/EdgeFunctionsListItem.utils.ts | 20 +++ ...ge-functions-last-hour-stats-query.test.ts | 104 +++++++++++++++ .../edge-functions-last-hour-stats-query.ts | 119 ++++++++++++++++++ apps/studio/data/edge-functions/keys.ts | 11 ++ .../pages/project/[ref]/functions/index.tsx | 26 ++-- 7 files changed, 373 insertions(+), 15 deletions(-) create mode 100644 apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.utils.test.ts create mode 100644 apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.utils.ts create mode 100644 apps/studio/data/edge-functions/edge-functions-last-hour-stats-query.test.ts create mode 100644 apps/studio/data/edge-functions/edge-functions-last-hour-stats-query.ts diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx index 3aa2091400c..c913b150311 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx @@ -3,12 +3,19 @@ import { useParams } from 'common/hooks' import dayjs from 'dayjs' import { Check, Copy } from 'lucide-react' import { useRouter } from 'next/router' -import { useState } from 'react' -import { copyToClipboard, TableCell, TableRow } from 'ui' -import { TimestampInfo } from 'ui-patterns' +import { useMemo, useState, type MouseEvent } from 'react' +import { cn, copyToClipboard, TableCell, TableRow } from 'ui' +import { ShimmeringLoader, TimestampInfo } from 'ui-patterns' +import { formatErrorRate } from './EdgeFunctionsListItem.utils' import { useProjectApiUrl } from '@/data/config/project-endpoint-query' -import type { EdgeFunctionsResponse } from '@/data/edge-functions/edge-functions-query' +import { useEdgeFunctionsLastHourStatsQuery } from '@/data/edge-functions/edge-functions-last-hour-stats-query' +import { + useEdgeFunctionsQuery, + type EdgeFunctionsResponse, +} from '@/data/edge-functions/edge-functions-query' +import { normalizeFunctionIds } from '@/data/edge-functions/keys' +import { usePHFlag } from '@/hooks/ui/useFlag' import { createNavigationHandler } from '@/lib/navigation' interface EdgeFunctionsListItemProps { @@ -20,6 +27,9 @@ export const EdgeFunctionsListItem = ({ function: item }: EdgeFunctionsListItemP const { ref } = useParams() const [isCopied, setIsCopied] = useState(false) + const showEdgeFunctionsRequestMetrics = usePHFlag('edgeFunctionsRequestMetrics') === true + const showLastHourStats = IS_PLATFORM && showEdgeFunctionsRequestMetrics + const { data: endpoint } = useProjectApiUrl({ projectRef: ref }) const functionUrl = `${endpoint}/functions/v1/${item.slug}` @@ -28,6 +38,24 @@ export const EdgeFunctionsListItem = ({ function: item }: EdgeFunctionsListItemP router ) + const { data: functions } = useEdgeFunctionsQuery({ projectRef: ref }) + const functionIds = useMemo(() => { + if (!showLastHourStats || !functions) return [] + return normalizeFunctionIds(functions.map((item) => item.id)) + }, [functions, showLastHourStats]) + + // [Joshen] We may be paginating the edge functions query in the future + // So this will eventually need to be a list of visibleFunctionIds instead + debounced + const { + data: lastHourStatsAll, + isPending: isStatsPending, + isError: isStatsError, + } = useEdgeFunctionsLastHourStatsQuery( + { projectRef: ref, functionIds }, + { enabled: showLastHourStats } + ) + const lastHourStats = lastHourStatsAll?.[item.id] + return ( { - function onCopy(value: any) { + onClick={(event: MouseEvent) => { + function onCopy(value: string) { setIsCopied(true) copyToClipboard(value) setTimeout(() => setIsCopied(false), 3000) @@ -86,7 +114,48 @@ export const EdgeFunctionsListItem = ({ function: item }: EdgeFunctionsListItemP label={dayjs(item.updated_at).fromNow()} /> - + {showLastHourStats && ( + <> + + {isStatsPending ? ( + + ) : isStatsError ? ( +

+ - +

+ ) : ( +

+ {lastHourStats !== undefined ? lastHourStats.requestsCount.toLocaleString() : '-'} +

+ )} +
+ + {isStatsPending ? ( + + ) : isStatsError ? ( +

+ - +

+ ) : lastHourStats !== undefined ? ( + = 1 + ? 'text-destructive' + : lastHourStats.errorRate > 0.1 + ? 'text-warning' + : 'text-foreground-light' + )} + > + {formatErrorRate(lastHourStats.errorRate)} + + ) : ( +

-

+ )} +
+ + )} +

{item.version}