mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 02:15:05 +03:00
The function detail layout is now breadcrumbs plus tabs (PageNav), so the Logs and Code tabs get the full height below the tabs. The name, invoke URL and last deploy details move into a header on the Overview tab, which also hosts the chart period selector. Overview sections (invocations, errors, performance, usage) now share a default-width PageContainer instead of each nesting a full-width one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
263 lines
8.6 KiB
TypeScript
263 lines
8.6 KiB
TypeScript
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
|
import { IS_PLATFORM, useParams } from 'common'
|
|
import { ExternalLink } from 'lucide-react'
|
|
import { useRouter } from 'next/router'
|
|
import { useEffect, useMemo, useState } from 'react'
|
|
import { PageContainer } from 'ui-patterns/PageContainer'
|
|
|
|
import { EdgeFunctionIntervalSelector } from './EdgeFunctionIntervalSelector'
|
|
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 { EdgeFunctionOverviewHeader } from './EdgeFunctionOverviewHeader'
|
|
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: <ExternalLink 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 (
|
|
<>
|
|
<EdgeFunctionOverviewHeader
|
|
actions={
|
|
<EdgeFunctionIntervalSelector interval={interval} onIntervalChange={setInterval} />
|
|
}
|
|
/>
|
|
<PageContainer size="default">
|
|
<EdgeFunctionInvocationsSection
|
|
dateTimeFormat={dateTimeFormat}
|
|
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}
|
|
/>
|
|
</PageContainer>
|
|
</>
|
|
)
|
|
}
|