EdgeFunctionRecentInvocations.tsx139 lines · main
1import { useParams } from 'common'
2import { Clock, ExternalLink, RefreshCw } from 'lucide-react'
3import Link from 'next/link'
4import { useRouter } from 'next/router'
5import { Button, cn } from 'ui'
6import { Admonition, TimestampInfo } from 'ui-patterns'
7import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
8
9import { parseEdgeFunctionEventMessage } from './EdgeFunctionRecentInvocations.utils'
10import { LOGS_TABLES } from '@/components/interfaces/Settings/Logs/Logs.constants'
11import useLogsPreview from '@/hooks/analytics/useLogsPreview'
12
13interface EdgeFunctionRecentInvocationsProps {
14 functionId: string
15 functionSlug: string
16}
17
18export const EdgeFunctionRecentInvocations = ({
19 functionId,
20 functionSlug,
21}: EdgeFunctionRecentInvocationsProps) => {
22 const { ref } = useParams()
23 const router = useRouter()
24
25 const { logData, isLoading, isSuccess, refresh } = useLogsPreview({
26 projectRef: ref as string,
27 table: LOGS_TABLES.fn_edge,
28 filterOverride: { function_id: functionId },
29 limit: 10,
30 })
31
32 return (
33 <div className="flex flex-col gap-y-3">
34 <div className="flex items-center justify-between">
35 <div>
36 <p className="text-sm">Recent Invocations</p>
37 <p className="text-xs text-foreground-light">
38 Latest invocation requests for this function
39 </p>
40 </div>
41 <Button
42 type="default"
43 loading={isLoading}
44 disabled={isLoading}
45 icon={<RefreshCw size={14} />}
46 onClick={() => refresh()}
47 >
48 Refresh
49 </Button>
50 </div>
51
52 {isLoading && !isSuccess ? (
53 <GenericSkeletonLoader />
54 ) : logData.length === 0 ? (
55 <Admonition
56 type="note"
57 title="No recent invocations"
58 description="Invocation logs will appear here when requests are made to this function"
59 />
60 ) : (
61 <div className="border rounded-md divide-y overflow-hidden">
62 {logData.map((log) => {
63 const statusCode = String(log.status_code ?? '')
64 const method = String(log.method ?? '')
65 const executionTime = log.execution_time_ms
66 const is2xx = statusCode.startsWith('2')
67 const is4xx = statusCode.startsWith('4')
68 const is5xx = statusCode.startsWith('5')
69 const logUrl = `/project/${ref}/functions/${functionSlug}/invocations?log=${log.id}`
70
71 return (
72 <div
73 key={log.id}
74 role="button"
75 tabIndex={0}
76 onClick={() => router.push(logUrl)}
77 onKeyDown={(e) => {
78 if (e.key === 'Enter' || e.key === ' ') {
79 e.preventDefault()
80 router.push(logUrl)
81 }
82 }}
83 className="group flex items-center font-mono px-3 py-2 gap-3 bg-surface-100 cursor-pointer hover:bg-surface-200 transition-colors"
84 >
85 <span className="text-xs text-foreground-light whitespace-nowrap">
86 <TimestampInfo utcTimestamp={log.timestamp!} format="DD MMM YY, HH:mm:ss" />
87 </span>
88 <div className="flex items-center">
89 {statusCode ? (
90 <div
91 className={cn(
92 'flex items-center justify-center border px-1.5 py-0.5 rounded-sm text-xs font-mono',
93 is2xx && 'text-brand border-brand bg-brand-300',
94 is4xx && 'text-warning border-warning bg-warning-300',
95 is5xx && 'text-destructive border-destructive bg-destructive-300',
96 !is2xx &&
97 !is4xx &&
98 !is5xx &&
99 'text-foreground-light border-default bg-surface-200'
100 )}
101 >
102 {statusCode}
103 </div>
104 ) : (
105 <span className="text-xs text-foreground-lighter">-</span>
106 )}
107 </div>
108 <span className="text-xs text-foreground-light">{method || '-'}</span>
109 {executionTime !== undefined && (
110 <span className="flex items-center gap-1 text-xs text-foreground-light">
111 <Clock size={12} className="text-foreground-muted" />
112 {Number(executionTime).toFixed(0)}ms
113 </span>
114 )}
115 <span className="flex-1 text-xs text-foreground-light truncate">
116 {parseEdgeFunctionEventMessage(
117 String(log.event_message ?? ''),
118 method,
119 statusCode
120 )}
121 </span>
122 <ExternalLink
123 size={14}
124 className="shrink-0 text-foreground-muted opacity-0 group-hover:opacity-100 transition-opacity"
125 />
126 </div>
127 )
128 })}
129 <Link
130 href={`/project/${ref}/functions/${functionSlug}/invocations`}
131 className="flex items-center justify-center py-2 text-xs text-foreground-light hover:text-foreground transition-colors"
132 >
133 View all invocations
134 </Link>
135 </div>
136 )}
137 </div>
138 )
139}