import {
flexRender,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
type ColumnDef,
type PaginationState,
type SortingState,
} from '@tanstack/react-table'
import { ChevronLeft, ChevronRight, RotateCcw, Search } from 'lucide-react'
import { useEffect, useState, type ReactNode } from 'react'
import {
Badge,
Button,
Card,
CardContent,
CardFooter,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from 'ui'
import { TimestampInfo } from 'ui-patterns'
import { Input } from 'ui-patterns/DataInputs/Input'
import { TanStackTableHeadSort } from 'ui-patterns/Table'
import type { WebhookDelivery, WebhookEndpoint } from './PlatformWebhooks.types'
import { statusBadgeVariant } from './PlatformWebhooksView.utils'
import { getStatusLevel } from '@/components/interfaces/UnifiedLogs/UnifiedLogs.utils'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import { DataTableColumnStatusCode } from '@/components/ui/DataTable/DataTableColumn/DataTableColumnStatusCode'
interface DetailItemProps {
label: string
children: ReactNode
ddClassName?: string
}
const DetailItem = ({ label, children, ddClassName = 'text-sm' }: DetailItemProps) => (
{label}
{children}
)
interface PlatformWebhooksEndpointDetailsProps {
deliverySearch: string
filteredDeliveries: WebhookDelivery[]
selectedEndpoint: WebhookEndpoint
onDeliverySearchChange: (value: string) => void
onOpenDelivery: (deliveryId: string) => void
onRetryDelivery: (deliveryId: string) => void
}
const DELIVERIES_PAGE_SIZE = 5
const DELIVERY_ACTIONS_COLUMN_ID = 'actions'
const DEFAULT_DELIVERY_SORTING: SortingState = [{ id: 'attemptAt', desc: true }]
const DELIVERY_COLUMNS: ColumnDef[] = [
{
accessorKey: 'status',
header: ({ column }) => Status,
cell: ({ row }) => (
{row.original.status}
),
},
{
accessorKey: 'eventType',
header: ({ column }) => (
Event type
),
cell: ({ row }) => {row.original.eventType},
},
{
accessorKey: 'responseCode',
header: ({ column }) => Response,
sortingFn: (rowA, rowB, columnId) => {
const responseA = rowA.getValue(columnId) ?? -1
const responseB = rowB.getValue(columnId) ?? -1
return responseA - responseB
},
cell: ({ row }) =>
row.original.responseCode != null ? (
) : (
–
),
},
{
accessorKey: 'attemptAt',
header: ({ column }) => (
Attempted
),
cell: ({ row }) => (
),
},
{
id: DELIVERY_ACTIONS_COLUMN_ID,
enableSorting: false,
header: () => Actions,
cell: ({ row, table }) => {
const { onRetryDelivery } = table.options.meta as {
onRetryDelivery: (deliveryId: string) => void
}
return (
{row.original.status !== 'success' ? (
}
aria-label={`Retry ${row.original.id}`}
tooltip={{ content: { side: 'top', text: 'Retry' } }}
onClick={(event) => {
event.stopPropagation()
onRetryDelivery(row.original.id)
}}
onKeyDown={(event) => event.stopPropagation()}
/>
) : (
}
aria-hidden
tabIndex={-1}
/>
)}
)
},
},
]
export const PlatformWebhooksEndpointDetails = ({
deliverySearch,
filteredDeliveries,
selectedEndpoint,
onDeliverySearchChange,
onOpenDelivery,
onRetryDelivery,
}: PlatformWebhooksEndpointDetailsProps) => {
const hasCustomHeaders = selectedEndpoint.customHeaders.length > 0
const hasName = selectedEndpoint.name.trim().length > 0
const hasDescription = selectedEndpoint.description.trim().length > 0
const [sorting, setSorting] = useState(DEFAULT_DELIVERY_SORTING)
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: DELIVERIES_PAGE_SIZE,
})
const table = useReactTable({
data: filteredDeliveries,
columns: DELIVERY_COLUMNS,
state: { pagination, sorting },
meta: { onRetryDelivery },
getRowId: (row) => row.id,
onPaginationChange: setPagination,
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
})
const paginatedDeliveries = table.getRowModel().rows
const deliveryStartIndex =
table.getState().pagination.pageIndex * table.getState().pagination.pageSize
const deliveryRangeStart = filteredDeliveries.length === 0 ? 0 : deliveryStartIndex + 1
const deliveryRangeEnd = Math.min(
deliveryStartIndex + table.getState().pagination.pageSize,
filteredDeliveries.length
)
useEffect(() => {
setPagination((currentPagination) => ({ ...currentPagination, pageIndex: 0 }))
}, [deliverySearch, selectedEndpoint.id])
return (
Overview
{hasName && {selectedEndpoint.name}}
{selectedEndpoint.url}
{hasDescription && (
{selectedEndpoint.description}
)}
{(selectedEndpoint.eventTypes.includes('*')
? ['All events (*)']
: selectedEndpoint.eventTypes
).map((eventType) => (
{eventType}
))}
{hasCustomHeaders && (
{selectedEndpoint.customHeaders.map((header) => (
{header.key}:
{header.value}
))}
)}
{selectedEndpoint.createdBy}
Deliveries
}
value={deliverySearch}
className="w-full lg:w-52"
onChange={(event) => onDeliverySearchChange(event.target.value)}
/>
{table.getHeaderGroups().map((headerGroup) => (
{headerGroup.headers.map((header) => {
const columnId = header.column.id
const sort = header.column.getIsSorted()
return (
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
)
})}
))}
{paginatedDeliveries.length > 0 ? (
paginatedDeliveries.map((row) => (
onOpenDelivery(row.original.id)}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
onOpenDelivery(row.original.id)
}
}}
tabIndex={0}
>
{row.getVisibleCells().map((cell) => (
{flexRender(cell.column.columnDef.cell, cell.getContext())}
))}
))
) : (
No deliveries found
Try adjusting your search to see more webhook attempts.
)}
{filteredDeliveries.length > 0 && (
Showing {deliveryRangeStart} to {deliveryRangeEnd} of {filteredDeliveries.length}{' '}
deliveries
}
className="w-7 hit-area-2"
aria-label="Previous page"
type="default"
size="tiny"
disabled={!table.getCanPreviousPage()}
onClick={() => table.previousPage()}
/>
}
className="w-7 hit-area-2"
aria-label="Next page"
type="default"
size="tiny"
disabled={!table.getCanNextPage()}
onClick={() => table.nextPage()}
/>
)}
)
}