EdgeFunctionRecentInvocations.tsx 5.3 KB

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