2024-01-16 14:38:05 +00:00
|
|
|
import { assert } from '@tldraw/utils'
|
|
|
|
import { GetServerSideProps } from 'next'
|
|
|
|
import { useEffect, useState } from 'react'
|
|
|
|
import { getAppOctokit } from '../src/octokit'
|
|
|
|
|
2024-05-22 15:55:49 +00:00
|
|
|
interface Props {
|
2024-01-16 14:38:05 +00:00
|
|
|
deliveries: {
|
|
|
|
id: number
|
|
|
|
guid: string
|
|
|
|
delivered_at: string
|
|
|
|
redelivery: boolean
|
|
|
|
duration: number
|
|
|
|
status: string
|
|
|
|
status_code: number
|
|
|
|
event: string
|
|
|
|
action: string | null
|
|
|
|
installation_id: number | null
|
|
|
|
repository_id: number | null
|
|
|
|
}[]
|
|
|
|
cursor: string | null
|
|
|
|
}
|
|
|
|
|
|
|
|
export const getServerSideProps: GetServerSideProps<Props> = async (context) => {
|
|
|
|
assert(process.env.NODE_ENV !== 'production')
|
|
|
|
|
|
|
|
const gh = getAppOctokit()
|
|
|
|
const deliveries = await gh.octokit.rest.apps.listWebhookDeliveries({
|
|
|
|
per_page: 100,
|
|
|
|
cursor: (context.query.cursor as string) ?? undefined,
|
|
|
|
})
|
|
|
|
|
|
|
|
const nextLinkMatch = deliveries.headers.link?.match(/(?<=<)([\S]*)(?=>; rel="Next")/i)
|
|
|
|
let cursor: string | null = null
|
|
|
|
if (nextLinkMatch) {
|
|
|
|
const url = new URL(nextLinkMatch[0])
|
|
|
|
cursor = url.searchParams.get('cursor')
|
|
|
|
}
|
|
|
|
|
|
|
|
return { props: { deliveries: deliveries.data, cursor } }
|
|
|
|
}
|
|
|
|
|
2024-05-22 15:55:49 +00:00
|
|
|
interface SelectedDelivery {
|
2024-01-16 14:38:05 +00:00
|
|
|
id: number
|
|
|
|
data?: unknown
|
|
|
|
}
|
|
|
|
|
|
|
|
export default function Deliveries({ deliveries, cursor }: Props) {
|
|
|
|
const [selectedDelivery, setSelectedDelivery] = useState<SelectedDelivery | null>(null)
|
|
|
|
const [isSimulating, setIsSimulating] = useState(false)
|
|
|
|
const [isRedelivering, setIsRedelivering] = useState(false)
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (!selectedDelivery || (selectedDelivery && selectedDelivery.data)) return
|
|
|
|
|
|
|
|
let cancelled = false
|
|
|
|
;(async () => {
|
|
|
|
const response = await fetch(`/api/dev/getDelivery?id=${selectedDelivery.id}`)
|
|
|
|
const data = await response.json()
|
|
|
|
if (cancelled) return
|
|
|
|
setSelectedDelivery({ id: selectedDelivery.id, data })
|
|
|
|
})()
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
cancelled = true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className="absolute inset-0 flex h-screen flex-col overflow-hidden">
|
|
|
|
<h1 className="flex-none border-b px-5 py-4 text-xl">Deliveries</h1>
|
|
|
|
<div className="flex flex-auto overflow-hidden">
|
|
|
|
<ol className="w-96 flex-none overflow-auto border-r">
|
|
|
|
{deliveries.map((delivery) => (
|
|
|
|
<li
|
|
|
|
key={delivery.id}
|
|
|
|
className={`border-b px-5 py-3 ${
|
|
|
|
selectedDelivery?.id === delivery.id ? 'bg-blue-100' : 'hover:bg-gray-50'
|
|
|
|
}`}
|
|
|
|
onClick={() => setSelectedDelivery({ id: delivery.id })}
|
|
|
|
>
|
|
|
|
<div className="font-semibold">
|
|
|
|
{delivery.event}
|
|
|
|
{delivery.action && `.${delivery.action}`}
|
|
|
|
</div>
|
|
|
|
<div className="text-sm">{formatDate(new Date(delivery.delivered_at))}</div>
|
|
|
|
</li>
|
|
|
|
))}
|
|
|
|
<li>
|
|
|
|
<a
|
|
|
|
className="center flex items-center justify-center border-b px-5 py-3 hover:bg-gray-50"
|
|
|
|
href={`?cursor=${cursor}`}
|
|
|
|
>
|
|
|
|
Load more...
|
|
|
|
</a>
|
|
|
|
</li>
|
|
|
|
</ol>
|
|
|
|
{selectedDelivery && selectedDelivery.data ? (
|
|
|
|
<div className="relative flex-auto overflow-hidden text-sm">
|
|
|
|
<pre className="absolute inset-0 overflow-auto whitespace-pre-wrap p-4">
|
2024-01-28 21:13:47 +00:00
|
|
|
{JSON.stringify(selectedDelivery.data, null, '\t')}
|
2024-01-16 14:38:05 +00:00
|
|
|
</pre>
|
|
|
|
<div className="absolute right-8 top-4 flex gap-3">
|
|
|
|
<button
|
|
|
|
className="center w-24 rounded bg-gray-100 px-4 py-2 text-gray-700 hover:bg-gray-200"
|
|
|
|
onClick={async () => {
|
|
|
|
setIsRedelivering(true)
|
|
|
|
try {
|
|
|
|
await fetch(`/api/dev/redeliver`, {
|
|
|
|
method: 'POST',
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
},
|
|
|
|
body: JSON.stringify({ id: selectedDelivery.id }),
|
|
|
|
})
|
|
|
|
} finally {
|
|
|
|
setIsRedelivering(false)
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
disabled={isRedelivering}
|
|
|
|
>
|
|
|
|
{isRedelivering ? '...' : 'Redeliver'}
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
className="center w-24 rounded bg-gray-100 px-4 py-2 text-gray-700 hover:bg-gray-200"
|
|
|
|
onClick={async () => {
|
|
|
|
setIsSimulating(true)
|
|
|
|
try {
|
|
|
|
const response = await fetch(`/api/dev/simulate`, {
|
|
|
|
method: 'POST',
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
},
|
|
|
|
body: JSON.stringify({ id: selectedDelivery.id }),
|
|
|
|
})
|
|
|
|
const { message } = await response.json()
|
|
|
|
alert(message)
|
|
|
|
} finally {
|
|
|
|
setIsSimulating(false)
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
disabled={isSimulating}
|
|
|
|
>
|
|
|
|
{isSimulating ? '...' : 'Simulate'}
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
) : selectedDelivery ? (
|
|
|
|
<div className="center flex flex-auto items-center justify-center text-xl text-gray-300">
|
|
|
|
loading...
|
|
|
|
</div>
|
|
|
|
) : null}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
function formatDate(date: Date) {
|
|
|
|
const intl = new Intl.DateTimeFormat('en-GB', {
|
|
|
|
dateStyle: 'short',
|
|
|
|
timeStyle: 'short',
|
|
|
|
})
|
|
|
|
|
|
|
|
return intl.format(date)
|
|
|
|
}
|