EdgeFunctionOverview.tsx251 lines · main
| 1 | import { PermissionAction } from '@supabase/shared-types/out/constants' |
| 2 | import { IS_PLATFORM, useParams } from 'common' |
| 3 | import { ExternalLink } from 'lucide-react' |
| 4 | import { useRouter } from 'next/router' |
| 5 | import { useEffect, useMemo, useState } from 'react' |
| 6 | |
| 7 | import { EdgeFunctionInvocationsSection } from './EdgeFunctionInvocationsSection' |
| 8 | import { |
| 9 | EDGE_FUNCTION_CHART_INTERVALS, |
| 10 | getBucketedTimeRange, |
| 11 | getExecutionMetrics, |
| 12 | getInvocationChartData, |
| 13 | getInvocationTotals, |
| 14 | getInvocationUpdateAnnotation, |
| 15 | getRollingTimeRange, |
| 16 | getUsageMetrics, |
| 17 | toEdgeFunctionChartData, |
| 18 | } from './EdgeFunctionOverview.utils' |
| 19 | import type { EdgeFunctionChartRawDatum } from './EdgeFunctionOverview.utils' |
| 20 | import { EdgeFunctionPerformanceSection } from './EdgeFunctionPerformanceSection' |
| 21 | import { EdgeFunctionRecentErrors } from './EdgeFunctionRecentErrors' |
| 22 | import { EdgeFunctionUsageSection } from './EdgeFunctionUsageSection' |
| 23 | import { useEdgeFunctionOverviewShortcuts } from './useEdgeFunctionOverviewShortcuts' |
| 24 | import { useUnifiedLogsPreview } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext' |
| 25 | import NoPermission from '@/components/ui/NoPermission' |
| 26 | import { |
| 27 | FunctionsCombinedStatsVariables, |
| 28 | useFunctionsCombinedStatsQuery, |
| 29 | } from '@/data/analytics/functions-combined-stats-query' |
| 30 | import { useEdgeFunctionQuery } from '@/data/edge-functions/edge-function-query' |
| 31 | import { useFillTimeseriesSorted } from '@/hooks/analytics/useFillTimeseriesSorted' |
| 32 | import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' |
| 33 | |
| 34 | export const EdgeFunctionOverview = () => { |
| 35 | const router = useRouter() |
| 36 | const { ref: projectRef, functionSlug } = useParams() |
| 37 | const { isEnabled: isUnifiedLogsEnabled } = useUnifiedLogsPreview() |
| 38 | |
| 39 | const [interval, setInterval] = useState<string>('15min') |
| 40 | const selectedInterval = |
| 41 | EDGE_FUNCTION_CHART_INTERVALS.find((item) => item.key === interval) || |
| 42 | EDGE_FUNCTION_CHART_INTERVALS[1] |
| 43 | const { |
| 44 | data: selectedFunction, |
| 45 | error: functionError, |
| 46 | isPending: isLoadingFunction, |
| 47 | isError: isErrorFunction, |
| 48 | } = useEdgeFunctionQuery({ |
| 49 | projectRef, |
| 50 | slug: functionSlug, |
| 51 | }) |
| 52 | const id = selectedFunction?.id |
| 53 | const combinedStatsResults = useFunctionsCombinedStatsQuery( |
| 54 | { |
| 55 | projectRef, |
| 56 | functionId: id, |
| 57 | interval: selectedInterval.key as FunctionsCombinedStatsVariables['interval'], |
| 58 | }, |
| 59 | { |
| 60 | enabled: IS_PLATFORM, |
| 61 | } |
| 62 | ) |
| 63 | |
| 64 | const combinedStatsData = useMemo( |
| 65 | () => (combinedStatsResults.data?.result as EdgeFunctionChartRawDatum[] | undefined) || [], |
| 66 | [combinedStatsResults.data] |
| 67 | ) |
| 68 | |
| 69 | const [startDate, endDate] = useMemo( |
| 70 | () => getBucketedTimeRange(selectedInterval), |
| 71 | [selectedInterval] |
| 72 | ) |
| 73 | const [selectedWindowStart, selectedWindowEnd] = useMemo( |
| 74 | () => getRollingTimeRange(selectedInterval), |
| 75 | [selectedInterval] |
| 76 | ) |
| 77 | const dateTimeFormat = selectedInterval.format ?? 'MMM D, h:mma' |
| 78 | |
| 79 | const { |
| 80 | data: combinedStatsChartData, |
| 81 | error: combinedStatsError, |
| 82 | isError: isErrorCombinedStats, |
| 83 | } = useFillTimeseriesSorted({ |
| 84 | data: combinedStatsData, |
| 85 | timestampKey: 'timestamp', |
| 86 | valueKey: [ |
| 87 | 'requests_count', |
| 88 | 'log_count', |
| 89 | 'log_info_count', |
| 90 | 'log_warn_count', |
| 91 | 'log_error_count', |
| 92 | 'success_count', |
| 93 | 'redirect_count', |
| 94 | 'client_err_count', |
| 95 | 'server_err_count', |
| 96 | 'avg_cpu_time_used', |
| 97 | 'avg_memory_used', |
| 98 | 'avg_execution_time', |
| 99 | 'max_execution_time', |
| 100 | 'avg_heap_memory_used', |
| 101 | 'avg_external_memory_used', |
| 102 | 'max_cpu_time_used', |
| 103 | ], |
| 104 | defaultValue: 0, |
| 105 | startDate: startDate.toISOString(), |
| 106 | endDate: endDate.toISOString(), |
| 107 | }) |
| 108 | |
| 109 | const chartData = useMemo( |
| 110 | () => toEdgeFunctionChartData(combinedStatsChartData), |
| 111 | [combinedStatsChartData] |
| 112 | ) |
| 113 | const invocationChartData = useMemo(() => getInvocationChartData(chartData), [chartData]) |
| 114 | const { totalInvocationCount, totalWarningCount, totalErrorCount } = useMemo( |
| 115 | () => getInvocationTotals(invocationChartData), |
| 116 | [invocationChartData] |
| 117 | ) |
| 118 | const { averageExecutionTime, maxExecutionTime } = useMemo( |
| 119 | () => getExecutionMetrics(chartData), |
| 120 | [chartData] |
| 121 | ) |
| 122 | const { |
| 123 | averageCpuTime, |
| 124 | maxCpuTime, |
| 125 | averageMemoryUsage, |
| 126 | totalHeapMemory, |
| 127 | totalExternalMemory, |
| 128 | totalMemoryByType, |
| 129 | } = useMemo(() => getUsageMetrics(chartData), [chartData]) |
| 130 | const invocationUpdateAnnotation = useMemo( |
| 131 | () => |
| 132 | getInvocationUpdateAnnotation({ |
| 133 | updatedAt: |
| 134 | selectedFunction?.updated_at === undefined |
| 135 | ? undefined |
| 136 | : String(selectedFunction.updated_at), |
| 137 | invocationChartData, |
| 138 | windowStart: selectedWindowStart, |
| 139 | windowEnd: selectedWindowEnd, |
| 140 | }), |
| 141 | [invocationChartData, selectedFunction?.updated_at, selectedWindowEnd, selectedWindowStart] |
| 142 | ) |
| 143 | |
| 144 | const invocationActions = useMemo( |
| 145 | () => [ |
| 146 | { |
| 147 | label: isUnifiedLogsEnabled ? 'Open logs' : 'Open invocations', |
| 148 | href: `/project/${projectRef}/functions/${functionSlug}/${ |
| 149 | isUnifiedLogsEnabled ? 'logs' : 'invocations' |
| 150 | }`, |
| 151 | icon: <ExternalLink size={12} />, |
| 152 | }, |
| 153 | ], |
| 154 | [functionSlug, isUnifiedLogsEnabled, projectRef] |
| 155 | ) |
| 156 | |
| 157 | useEdgeFunctionOverviewShortcuts({ |
| 158 | onSetInterval: setInterval, |
| 159 | onRefresh: () => { |
| 160 | combinedStatsResults.refetch() |
| 161 | }, |
| 162 | onOpenLogs: () => { |
| 163 | router.push( |
| 164 | `/project/${projectRef}/functions/${functionSlug}/${ |
| 165 | isUnifiedLogsEnabled ? 'logs' : 'invocations' |
| 166 | }` |
| 167 | ) |
| 168 | }, |
| 169 | }) |
| 170 | |
| 171 | const { isLoading: permissionsLoading, can: canReadFunction } = useAsyncCheckPermissions( |
| 172 | PermissionAction.FUNCTIONS_READ, |
| 173 | functionSlug as string |
| 174 | ) |
| 175 | |
| 176 | useEffect(() => { |
| 177 | if (!IS_PLATFORM && projectRef && functionSlug) { |
| 178 | router.replace(`/project/${projectRef}/functions/${functionSlug}/details`) |
| 179 | } |
| 180 | }, [functionSlug, projectRef, router]) |
| 181 | |
| 182 | if (!canReadFunction && !permissionsLoading) { |
| 183 | return <NoPermission isFullPage resourceText="access this edge function" /> |
| 184 | } |
| 185 | |
| 186 | if (!IS_PLATFORM) { |
| 187 | return null |
| 188 | } |
| 189 | |
| 190 | return ( |
| 191 | <> |
| 192 | <EdgeFunctionInvocationsSection |
| 193 | interval={interval} |
| 194 | onIntervalChange={setInterval} |
| 195 | selectedInterval={selectedInterval} |
| 196 | actions={invocationActions} |
| 197 | totalInvocationCount={totalInvocationCount} |
| 198 | totalErrorCount={totalErrorCount} |
| 199 | totalWarningCount={totalWarningCount} |
| 200 | isLoadingFunction={isLoadingFunction} |
| 201 | isErrorFunction={isErrorFunction} |
| 202 | functionError={functionError} |
| 203 | isLoadingChart={combinedStatsResults.isLoading} |
| 204 | isErrorChart={isErrorCombinedStats} |
| 205 | chartErrorMessage={combinedStatsError?.message ?? 'Unknown error'} |
| 206 | chartData={invocationChartData} |
| 207 | onChartClick={() => { |
| 208 | router.push( |
| 209 | `/project/${projectRef}/functions/${functionSlug}/${ |
| 210 | isUnifiedLogsEnabled ? 'logs' : 'invocations' |
| 211 | }${isUnifiedLogsEnabled ? '' : `?its=${startDate.toISOString()}`}` |
| 212 | ) |
| 213 | }} |
| 214 | updateAnnotation={invocationUpdateAnnotation} |
| 215 | /> |
| 216 | |
| 217 | <EdgeFunctionRecentErrors |
| 218 | functionId={id} |
| 219 | functionSlug={functionSlug as string} |
| 220 | projectRef={projectRef as string} |
| 221 | updatedAt={selectedFunction?.updated_at} |
| 222 | /> |
| 223 | |
| 224 | <EdgeFunctionPerformanceSection |
| 225 | data={chartData} |
| 226 | dateTimeFormat={dateTimeFormat} |
| 227 | isLoading={combinedStatsResults.isLoading} |
| 228 | isError={isErrorCombinedStats} |
| 229 | errorMessage={combinedStatsError?.message ?? 'Unknown error'} |
| 230 | averageExecutionTime={averageExecutionTime} |
| 231 | maxExecutionTime={maxExecutionTime} |
| 232 | /> |
| 233 | |
| 234 | <EdgeFunctionUsageSection |
| 235 | data={chartData} |
| 236 | dateTimeFormat={dateTimeFormat} |
| 237 | isLoading={combinedStatsResults.isLoading} |
| 238 | isError={isErrorCombinedStats} |
| 239 | errorMessage={combinedStatsError?.message ?? 'Unknown error'} |
| 240 | averageCpuTime={averageCpuTime} |
| 241 | maxCpuTime={maxCpuTime} |
| 242 | averageMemoryUsage={averageMemoryUsage} |
| 243 | totalHeapMemory={totalHeapMemory} |
| 244 | totalExternalMemory={totalExternalMemory} |
| 245 | totalMemoryByType={totalMemoryByType} |
| 246 | /> |
| 247 | </> |
| 248 | ) |
| 249 | } |
| 250 | |
| 251 | export default EdgeFunctionOverview |