| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- import { IS_PLATFORM, useFlag } from 'common'
- import { useParams } from 'common/hooks'
- import dayjs from 'dayjs'
- import { Check, Copy } from 'lucide-react'
- import { useRouter } from 'next/router'
- import { useMemo, useState, type MouseEvent } from 'react'
- import { cn, copyToClipboard, TableCell, TableRow } from 'ui'
- import { ShimmeringLoader, TimestampInfo } from 'ui-patterns'
- import { formatErrorRate } from './EdgeFunctionsListItem.utils'
- import { useProjectApiUrl } from '@/data/config/project-endpoint-query'
- import { useEdgeFunctionsLastHourStatsQuery } from '@/data/edge-functions/edge-functions-last-hour-stats-query'
- import {
- useEdgeFunctionsQuery,
- type EdgeFunctionsResponse,
- } from '@/data/edge-functions/edge-functions-query'
- import { normalizeFunctionIds } from '@/data/edge-functions/keys'
- import { createNavigationHandler } from '@/lib/navigation'
- interface EdgeFunctionsListItemProps {
- function: EdgeFunctionsResponse
- }
- export const EdgeFunctionsListItem = ({ function: item }: EdgeFunctionsListItemProps) => {
- const router = useRouter()
- const { ref } = useParams()
- const [isCopied, setIsCopied] = useState(false)
- const showLastHourStats = useFlag('edgeFunctionsRequestMetrics')
- const { data: endpoint } = useProjectApiUrl({ projectRef: ref })
- const functionUrl = `${endpoint}/functions/v1/${item.slug}`
- const handleNavigation = createNavigationHandler(
- `/project/${ref}/functions/${item.slug}${IS_PLATFORM ? '' : `/details`}`,
- router
- )
- const { data: functions } = useEdgeFunctionsQuery({ projectRef: ref })
- const functionIds = useMemo(() => {
- if (!showLastHourStats || !functions) return []
- return normalizeFunctionIds(functions.map((item) => item.id))
- }, [functions, showLastHourStats])
- // [Joshen] We may be paginating the edge functions query in the future
- // So this will eventually need to be a list of visibleFunctionIds instead + debounced
- const {
- data: lastHourStatsAll,
- isPending: isStatsPending,
- isError: isStatsError,
- } = useEdgeFunctionsLastHourStatsQuery(
- { projectRef: ref, functionIds },
- { enabled: showLastHourStats }
- )
- const lastHourStats = lastHourStatsAll?.[item.id]
- return (
- <TableRow
- key={item.id}
- onClick={handleNavigation}
- onAuxClick={handleNavigation}
- onKeyDown={handleNavigation}
- tabIndex={0}
- className="cursor-pointer inset-focus"
- >
- <TableCell>
- <p className="text-sm text-foreground whitespace-nowrap py-2">{item.name}</p>
- </TableCell>
- <TableCell>
- <div className="text-xs text-foreground-light flex gap-2 items-center truncate">
- <p title={functionUrl} className="font-mono truncate hidden md:inline max-w-120">
- {functionUrl}
- </p>
- <button
- type="button"
- className="text-foreground-lighter hover:text-foreground transition"
- onClick={(event: MouseEvent<HTMLButtonElement>) => {
- function onCopy(value: string) {
- setIsCopied(true)
- copyToClipboard(value)
- setTimeout(() => setIsCopied(false), 3000)
- }
- event.stopPropagation()
- onCopy(functionUrl)
- }}
- >
- {isCopied ? (
- <div className="text-brand">
- <Check size={14} strokeWidth={3} />
- </div>
- ) : (
- <div className="relative">
- <div className="block">
- <Copy size={14} strokeWidth={1.5} />
- </div>
- </div>
- )}
- </button>
- </div>
- </TableCell>
- <TableCell className="hidden 2xl:table-cell whitespace-nowrap">
- {item.created_at ? (
- <TimestampInfo
- className="text-sm text-foreground-light whitespace-nowrap"
- utcTimestamp={item.created_at}
- label={dayjs(item.created_at).fromNow()}
- />
- ) : (
- <span className="text-sm text-foreground-light">–</span>
- )}
- </TableCell>
- <TableCell className="lg:table-cell">
- {item.updated_at ? (
- <TimestampInfo
- className="text-sm text-foreground-light whitespace-nowrap"
- utcTimestamp={item.updated_at}
- label={dayjs(item.updated_at).fromNow()}
- />
- ) : (
- <span className="text-sm text-foreground-light">–</span>
- )}
- </TableCell>
- {showLastHourStats && (
- <>
- <TableCell className="lg:table-cell whitespace-nowrap">
- {isStatsPending ? (
- <ShimmeringLoader className="w-12" />
- ) : isStatsError ? (
- <p className="text-foreground-lighter" title="Failed to load stats">
- -
- </p>
- ) : (
- <p className="text-foreground-light">
- {lastHourStats !== undefined ? lastHourStats.requestsCount.toLocaleString() : '-'}
- </p>
- )}
- </TableCell>
- <TableCell className="lg:table-cell whitespace-nowrap">
- {isStatsPending ? (
- <ShimmeringLoader className="w-12" />
- ) : isStatsError ? (
- <p className="text-foreground-lighter" title="Failed to load stats">
- -
- </p>
- ) : lastHourStats !== undefined ? (
- <span
- className={cn(
- 'text-sm',
- lastHourStats.errorRate >= 1
- ? 'text-destructive'
- : lastHourStats.errorRate > 0.1
- ? 'text-warning'
- : 'text-foreground-light'
- )}
- >
- {formatErrorRate(lastHourStats.errorRate)}
- </span>
- ) : (
- <p className="text-foreground-lighter">-</p>
- )}
- </TableCell>
- </>
- )}
- <TableCell className="hidden 2xl:table-cell">
- <p className="text-foreground-light">{item.version}</p>
- <button tabIndex={-1} className="sr-only">
- Go to function details
- </button>
- </TableCell>
- </TableRow>
- )
- }
|