Files
supabase/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverview.tsx
T
Joshen Lim 09a1f431bc Mass update for all lucide icon imports to use the component with Icon suffix (#51659)
## Context

This is largely just a mechanical change here:
Opting to use lucide icons with the `Icon` suffix across all icon
imports within `studio`
- so that it’s clearer which components are icons + reduces the
possibility of name collision with our own components
- e.g <User /> vs <UserIcon />

Also added a eslint rule for this:
<img width="1069" height="167" alt="image"
src="https://github.com/user-attachments/assets/7f36f443-5ca2-4bf6-8d22-86e774513bfc"
/>

And updated `AGENTS.md` as such:
```
Icons come from `lucide-react`, always imported with the `Icon` suffix (`import { CheckIcon, Trash2Icon } from 'lucide-react'`) — never the bare names (`Check`) or `Check as CheckIcon` aliases (lint-enforced).
```
2026-10-14 12:56:55 +02:00

255 lines
8.1 KiB
TypeScript

import { PermissionAction } from '@supabase/shared-types/out/constants'
import { IS_PLATFORM, useParams } from 'common'
import { ExternalLinkIcon } from 'lucide-react'
import { useRouter } from 'next/router'
import { useEffect, useMemo, useState } from 'react'
import { EdgeFunctionInvocationsSection } from './EdgeFunctionInvocationsSection'
import {
EDGE_FUNCTION_CHART_INTERVALS,
getBucketedTimeRange,
getExecutionMetrics,
getInvocationChartData,
getInvocationChartNavigationUrl,
getInvocationTotals,
getInvocationUpdateAnnotation,
getRollingTimeRange,
getUsageMetrics,
toEdgeFunctionChartData,
} from './EdgeFunctionOverview.utils'
import type { EdgeFunctionChartRawDatum } from './EdgeFunctionOverview.utils'
import { EdgeFunctionPerformanceSection } from './EdgeFunctionPerformanceSection'
import { EdgeFunctionRecentErrors } from './EdgeFunctionRecentErrors'
import { EdgeFunctionUsageSection } from './EdgeFunctionUsageSection'
import { useEdgeFunctionOverviewShortcuts } from './useEdgeFunctionOverviewShortcuts'
import { useUnifiedLogsPreview } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { NoPermission } from '@/components/ui/NoPermission'
import {
FunctionsCombinedStatsVariables,
useFunctionsCombinedStatsQuery,
} from '@/data/analytics/functions-combined-stats-query'
import { useEdgeFunctionQuery } from '@/data/edge-functions/edge-function-query'
import { useFillTimeseriesSorted } from '@/hooks/analytics/useFillTimeseriesSorted'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
export const EdgeFunctionOverview = () => {
const router = useRouter()
const { ref: projectRef, functionSlug } = useParams()
const { isEnabled: isUnifiedLogsEnabled } = useUnifiedLogsPreview()
const [interval, setInterval] = useState<string>('15min')
const selectedInterval =
EDGE_FUNCTION_CHART_INTERVALS.find((item) => item.key === interval) ||
EDGE_FUNCTION_CHART_INTERVALS[1]
const {
data: selectedFunction,
error: functionError,
isPending: isLoadingFunction,
isError: isErrorFunction,
} = useEdgeFunctionQuery({
projectRef,
slug: functionSlug,
})
const id = selectedFunction?.id
const combinedStatsResults = useFunctionsCombinedStatsQuery(
{
projectRef,
functionId: id,
interval: selectedInterval.key as FunctionsCombinedStatsVariables['interval'],
},
{
enabled: IS_PLATFORM,
}
)
const combinedStatsData = useMemo(
() => (combinedStatsResults.data?.result as EdgeFunctionChartRawDatum[] | undefined) || [],
[combinedStatsResults.data]
)
const [startDate, endDate] = useMemo(
() => getBucketedTimeRange(selectedInterval),
[selectedInterval]
)
const [selectedWindowStart, selectedWindowEnd] = useMemo(
() => getRollingTimeRange(selectedInterval),
[selectedInterval]
)
const dateTimeFormat = selectedInterval.format ?? 'MMM D, h:mma'
const {
data: combinedStatsChartData,
error: combinedStatsError,
isError: isErrorCombinedStats,
} = useFillTimeseriesSorted({
data: combinedStatsData,
timestampKey: 'timestamp',
valueKey: [
'requests_count',
'log_count',
'log_info_count',
'log_warn_count',
'log_error_count',
'success_count',
'redirect_count',
'client_err_count',
'server_err_count',
'avg_cpu_time_used',
'avg_memory_used',
'avg_execution_time',
'max_execution_time',
'avg_heap_memory_used',
'avg_external_memory_used',
'max_cpu_time_used',
],
defaultValue: 0,
startDate: startDate.toISOString(),
endDate: endDate.toISOString(),
})
const chartData = useMemo(
() => toEdgeFunctionChartData(combinedStatsChartData),
[combinedStatsChartData]
)
const invocationChartData = useMemo(() => getInvocationChartData(chartData), [chartData])
const { totalInvocationCount, totalWarningCount, totalErrorCount } = useMemo(
() => getInvocationTotals(invocationChartData),
[invocationChartData]
)
const { averageExecutionTime, maxExecutionTime } = useMemo(
() => getExecutionMetrics(chartData),
[chartData]
)
const {
averageCpuTime,
maxCpuTime,
averageMemoryUsage,
totalHeapMemory,
totalExternalMemory,
totalMemoryByType,
} = useMemo(() => getUsageMetrics(chartData), [chartData])
const invocationUpdateAnnotation = useMemo(
() =>
getInvocationUpdateAnnotation({
updatedAt: selectedFunction?.updated_at,
invocationChartData,
windowStart: selectedWindowStart,
windowEnd: selectedWindowEnd,
}),
[invocationChartData, selectedFunction?.updated_at, selectedWindowEnd, selectedWindowStart]
)
const invocationActions = useMemo(
() => [
{
label: isUnifiedLogsEnabled ? 'Open logs' : 'Open invocations',
href: `/project/${projectRef}/functions/${functionSlug}/${
isUnifiedLogsEnabled ? 'logs' : 'invocations'
}`,
icon: <ExternalLinkIcon size={12} />,
},
],
[functionSlug, isUnifiedLogsEnabled, projectRef]
)
useEdgeFunctionOverviewShortcuts({
onSetInterval: setInterval,
onRefresh: () => {
combinedStatsResults.refetch()
},
onOpenLogs: () => {
router.push(
`/project/${projectRef}/functions/${functionSlug}/${
isUnifiedLogsEnabled ? 'logs' : 'invocations'
}`
)
},
})
const { isLoading: permissionsLoading, can: canReadFunction } = useAsyncCheckPermissions(
PermissionAction.FUNCTIONS_READ,
functionSlug as string
)
useEffect(() => {
if (!IS_PLATFORM && projectRef && functionSlug) {
router.replace(`/project/${projectRef}/functions/${functionSlug}/details`)
}
}, [functionSlug, projectRef, router])
if (!canReadFunction && !permissionsLoading) {
return <NoPermission isFullPage resourceText="access this edge function" />
}
if (!IS_PLATFORM) {
return null
}
return (
<>
<EdgeFunctionInvocationsSection
interval={interval}
onIntervalChange={setInterval}
selectedInterval={selectedInterval}
actions={invocationActions}
totalInvocationCount={totalInvocationCount}
totalErrorCount={totalErrorCount}
totalWarningCount={totalWarningCount}
isLoadingFunction={isLoadingFunction}
isErrorFunction={isErrorFunction}
functionError={functionError}
isLoadingChart={combinedStatsResults.isLoading}
isErrorChart={isErrorCombinedStats}
chartErrorMessage={combinedStatsError?.message ?? 'Unknown error'}
chartData={invocationChartData}
onChartClick={(timestamp) => {
if (!projectRef || !functionSlug) return
router.push(
getInvocationChartNavigationUrl({
projectRef,
functionSlug,
isUnifiedLogsEnabled,
rangeStart: startDate.toISOString(),
rangeEnd: endDate.toISOString(),
clickedTimestamp: timestamp,
})
)
}}
updateAnnotation={invocationUpdateAnnotation}
/>
<EdgeFunctionRecentErrors
functionId={id}
functionSlug={functionSlug as string}
projectRef={projectRef as string}
updatedAt={selectedFunction?.updated_at}
/>
<EdgeFunctionPerformanceSection
data={chartData}
dateTimeFormat={dateTimeFormat}
isLoading={combinedStatsResults.isLoading}
isError={isErrorCombinedStats}
errorMessage={combinedStatsError?.message ?? 'Unknown error'}
averageExecutionTime={averageExecutionTime}
maxExecutionTime={maxExecutionTime}
/>
<EdgeFunctionUsageSection
data={chartData}
dateTimeFormat={dateTimeFormat}
isLoading={combinedStatsResults.isLoading}
isError={isErrorCombinedStats}
errorMessage={combinedStatsError?.message ?? 'Unknown error'}
averageCpuTime={averageCpuTime}
maxCpuTime={maxCpuTime}
averageMemoryUsage={averageMemoryUsage}
totalHeapMemory={totalHeapMemory}
totalExternalMemory={totalExternalMemory}
totalMemoryByType={totalMemoryByType}
/>
</>
)
}