EdgeFunctionOverview.tsx251 lines · main
1import { PermissionAction } from '@supabase/shared-types/out/constants'
2import { IS_PLATFORM, useParams } from 'common'
3import { ExternalLink } from 'lucide-react'
4import { useRouter } from 'next/router'
5import { useEffect, useMemo, useState } from 'react'
6
7import { EdgeFunctionInvocationsSection } from './EdgeFunctionInvocationsSection'
8import {
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'
19import type { EdgeFunctionChartRawDatum } from './EdgeFunctionOverview.utils'
20import { EdgeFunctionPerformanceSection } from './EdgeFunctionPerformanceSection'
21import { EdgeFunctionRecentErrors } from './EdgeFunctionRecentErrors'
22import { EdgeFunctionUsageSection } from './EdgeFunctionUsageSection'
23import { useEdgeFunctionOverviewShortcuts } from './useEdgeFunctionOverviewShortcuts'
24import { useUnifiedLogsPreview } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
25import NoPermission from '@/components/ui/NoPermission'
26import {
27 FunctionsCombinedStatsVariables,
28 useFunctionsCombinedStatsQuery,
29} from '@/data/analytics/functions-combined-stats-query'
30import { useEdgeFunctionQuery } from '@/data/edge-functions/edge-function-query'
31import { useFillTimeseriesSorted } from '@/hooks/analytics/useFillTimeseriesSorted'
32import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
33
34export 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
251export default EdgeFunctionOverview