mirror of
https://github.com/supabase/supabase.git
synced 2026-10-19 08:09:11 +03:00
fix: align webhook FE with updated API changes (#51800)
## What Aligns the webhooks FE with backend changes confirmed by Pawel (FE-4621): 1. `custom_headers` is optional but never null, matches the updated contract. 2. Removed the client-only `name` field (never persisted by the API). Display name is now the endpoint's URL plus its subscribed event types. 3. The deliveries list endpoint now returns the full `event` object, so `eventType`/`eventPayload` are always populated. Added an Event column to the deliveries table. Also regenerates `packages/api-types` from production to pick up these changes. While testing, found and fixed two more related issues: 4. A delivery can briefly exist before the backend finishes attaching its event data, regardless of what triggered it (test, retry, or real traffic). The deliveries list now polls every 5s while open, so new or still-settling deliveries fill in automatically instead of requiring a manual refresh. 5. "Send test event" always defaulted to an unrelated sample event type. The test endpoint actually accepts a specific `event_type`, so this now sends the endpoint's own subscribed type automatically, or shows a picker when there's more than one to choose from. ## Testing All webhooks component/unit tests pass (30/30), typecheck and lint clean. Manually verified on localhost against a live backend. ## Note This may clash with #51614 (`custom_headers: null` in a test fixture, no longer a valid value once this merges). Will follow up there after this lands.
This commit is contained in:
1 parent
0e5753aac7
commit
d0cf8dbc50
18 files changed
+395
-337
No files matched your search
@@ -12,8 +12,6 @@ export interface WebhookHeader {
|
||||
|
||||
export interface WebhookEndpoint {
|
||||
id: string
|
||||
// No `name` field in the real API — client-only until backend adds one.
|
||||
name: string
|
||||
url: string
|
||||
description: string
|
||||
enabled: boolean
|
||||
@@ -27,10 +25,8 @@ export interface WebhookDelivery {
|
||||
id: string
|
||||
endpointId: string
|
||||
eventId: string
|
||||
// Only present when fetched via the single-delivery endpoint (drill-down view) —
|
||||
// the deliveries *list* endpoint doesn't return either, so list rows leave these unset.
|
||||
eventType?: string
|
||||
eventPayload?: Record<string, unknown>
|
||||
eventType: string
|
||||
eventPayload: Record<string, unknown>
|
||||
status: WebhookDeliveryStatus
|
||||
responseCode: number
|
||||
responseBody: string | Record<string, string> | null
|
||||
@@ -50,7 +46,6 @@ export interface PlatformWebhooksState {
|
||||
}
|
||||
|
||||
export interface UpsertWebhookEndpointInput {
|
||||
name: string
|
||||
url: string
|
||||
description: string
|
||||
enabled: boolean
|
||||
|
||||
+12
-23
@@ -1,16 +1,14 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { WebhookDelivery, WebhookEndpoint } from './PlatformWebhooks.types'
|
||||
import {
|
||||
filterWebhookDeliveries,
|
||||
filterWebhookEndpoints,
|
||||
generateWebhookEndpointName,
|
||||
getWebhookEndpointDisplayName,
|
||||
} from './PlatformWebhooks.utils'
|
||||
|
||||
const createEndpoint = (overrides: Partial<WebhookEndpoint> = {}): WebhookEndpoint => ({
|
||||
id: 'endpoint-1',
|
||||
name: '',
|
||||
url: 'https://hooks.example.com/billing',
|
||||
description: '',
|
||||
enabled: true,
|
||||
@@ -25,6 +23,8 @@ const createDelivery = (overrides: Partial<WebhookDelivery> = {}): WebhookDelive
|
||||
id: 'delivery-1',
|
||||
endpointId: 'endpoint-1',
|
||||
eventId: 'event-1',
|
||||
eventType: 'v1.project.updated',
|
||||
eventPayload: {},
|
||||
status: 'success',
|
||||
responseCode: 200,
|
||||
responseBody: null,
|
||||
@@ -34,40 +34,29 @@ const createDelivery = (overrides: Partial<WebhookDelivery> = {}): WebhookDelive
|
||||
})
|
||||
|
||||
describe('PlatformWebhooks.utils', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('returns the trimmed endpoint name when present', () => {
|
||||
it('combines the endpoint url and subscribed event types', () => {
|
||||
expect(
|
||||
getWebhookEndpointDisplayName({
|
||||
name: ' Billing events ',
|
||||
url: 'https://hooks.example.com/billing',
|
||||
eventTypes: ['v1.project.paused', 'v1.project.created'],
|
||||
})
|
||||
).toBe('Billing events')
|
||||
).toBe('https://hooks.example.com/billing (v1.project.paused, v1.project.created)')
|
||||
})
|
||||
|
||||
it('falls back to the endpoint url when the name is empty', () => {
|
||||
it('shows "all events" when subscribed to every event type', () => {
|
||||
expect(
|
||||
getWebhookEndpointDisplayName({
|
||||
name: ' ',
|
||||
url: 'https://hooks.example.com/fallback',
|
||||
url: 'https://hooks.example.com/billing',
|
||||
eventTypes: ['*'],
|
||||
})
|
||||
).toBe('https://hooks.example.com/fallback')
|
||||
).toBe('https://hooks.example.com/billing (all events)')
|
||||
})
|
||||
|
||||
it('generates an adjective-noun endpoint name', () => {
|
||||
vi.spyOn(Math, 'random').mockReturnValueOnce(0).mockReturnValueOnce(0.1)
|
||||
|
||||
expect(generateWebhookEndpointName()).toBe('swift-courier')
|
||||
})
|
||||
|
||||
it('filters endpoints by name, url, and description', () => {
|
||||
it('filters endpoints by url and description', () => {
|
||||
const endpoints = [
|
||||
createEndpoint({ id: '1', name: 'Billing events', url: 'https://hooks.example.com/billing' }),
|
||||
createEndpoint({ id: '1', url: 'https://hooks.example.com/billing' }),
|
||||
createEndpoint({
|
||||
id: '2',
|
||||
name: 'Slack alerts',
|
||||
url: 'https://hooks.slack.com/x',
|
||||
description: 'operational alerts',
|
||||
}),
|
||||
|
||||
@@ -1,50 +1,5 @@
|
||||
import type { WebhookDelivery, WebhookEndpoint } from './PlatformWebhooks.types'
|
||||
|
||||
const WEBHOOK_NAME_ADJECTIVES = [
|
||||
'swift',
|
||||
'winged',
|
||||
'wayfinding',
|
||||
'moonlit',
|
||||
'fleet',
|
||||
'nimble',
|
||||
'roving',
|
||||
'brisk',
|
||||
'gliding',
|
||||
'steady',
|
||||
'northbound',
|
||||
'starlit',
|
||||
'quiet',
|
||||
'amber',
|
||||
'far-flung',
|
||||
'secret',
|
||||
'flying',
|
||||
]
|
||||
|
||||
const WEBHOOK_NAME_NOUNS = [
|
||||
'pigeon',
|
||||
'courier',
|
||||
'postmark',
|
||||
'relay',
|
||||
'dispatch',
|
||||
'lantern',
|
||||
'beacon',
|
||||
'messenger',
|
||||
'waypost',
|
||||
'sparrow',
|
||||
'satchel',
|
||||
'signalfire',
|
||||
'envelope',
|
||||
'parcel',
|
||||
'gull',
|
||||
'kite',
|
||||
'beagle',
|
||||
]
|
||||
|
||||
const getRandomItem = (values: string[]) => values[Math.floor(Math.random() * values.length)]
|
||||
|
||||
export const generateWebhookEndpointName = () =>
|
||||
`${getRandomItem(WEBHOOK_NAME_ADJECTIVES)}-${getRandomItem(WEBHOOK_NAME_NOUNS)}`
|
||||
|
||||
const secureRandomHex = (length: number) => {
|
||||
if (length <= 0) return ''
|
||||
|
||||
@@ -64,8 +19,19 @@ const secureRandomHex = (length: number) => {
|
||||
|
||||
export const generateSigningSecret = () => `whsec_${secureRandomHex(16)}`
|
||||
|
||||
export const getWebhookEndpointDisplayName = (endpoint: Pick<WebhookEndpoint, 'name' | 'url'>) =>
|
||||
endpoint.name.trim() || endpoint.url
|
||||
export const getWebhookEndpointDisplayName = (
|
||||
endpoint: Pick<WebhookEndpoint, 'url' | 'eventTypes'>
|
||||
) => {
|
||||
const eventTypesLabel = endpoint.eventTypes.includes('*')
|
||||
? 'all events'
|
||||
: endpoint.eventTypes.join(', ')
|
||||
return `${endpoint.url} (${eventTypesLabel})`
|
||||
}
|
||||
|
||||
export const getTestWebhookEventType = (
|
||||
subscribedEventTypes: string[],
|
||||
fallbackEventTypes: string[]
|
||||
) => subscribedEventTypes.find((eventType) => eventType !== '*') ?? fallbackEventTypes[0]
|
||||
|
||||
const normalizeSearch = (value: string) => value.trim().toLowerCase()
|
||||
|
||||
|
||||
+1
-1
@@ -46,7 +46,6 @@ const SELECTED_ENDPOINT_ID = '7f2c9d4a-6e31-4d9d-9a1f-2c4b5e6f7081'
|
||||
|
||||
const selectedEndpoint: WebhookEndpoint = {
|
||||
id: SELECTED_ENDPOINT_ID,
|
||||
name: 'Lovable production',
|
||||
url: 'https://api.lovable.dev/webhooks/supabase',
|
||||
description: 'Primary organization webhook endpoint',
|
||||
enabled: true,
|
||||
@@ -61,6 +60,7 @@ const createDelivery = (overrides: Partial<WebhookDelivery>): WebhookDelivery =>
|
||||
endpointId: SELECTED_ENDPOINT_ID,
|
||||
eventId: 'event',
|
||||
eventType: 'v1.project.updated',
|
||||
eventPayload: {},
|
||||
status: 'success',
|
||||
responseCode: 200,
|
||||
responseBody: null,
|
||||
|
||||
+5
-3
@@ -78,6 +78,11 @@ const DELIVERY_COLUMNS: ColumnDef<WebhookDelivery>[] = [
|
||||
<Badge variant={statusBadgeVariant[row.original.status]}>{row.original.status}</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'eventType',
|
||||
header: ({ column }) => <TanStackTableHeadSort column={column}>Event</TanStackTableHeadSort>,
|
||||
cell: ({ row }) => <code className="text-code-inline text-xs">{row.original.eventType}</code>,
|
||||
},
|
||||
{
|
||||
accessorKey: 'responseCode',
|
||||
header: ({ column }) => <TanStackTableHeadSort column={column}>Response</TanStackTableHeadSort>,
|
||||
@@ -158,7 +163,6 @@ export const PlatformWebhooksEndpointDetails = ({
|
||||
onRetryDelivery,
|
||||
}: PlatformWebhooksEndpointDetailsProps) => {
|
||||
const hasCustomHeaders = selectedEndpoint.customHeaders.length > 0
|
||||
const hasName = selectedEndpoint.name.trim().length > 0
|
||||
const hasDescription = selectedEndpoint.description.trim().length > 0
|
||||
const deliverySearchRef = useRef<HTMLInputElement>(null)
|
||||
const [sorting, setSorting] = useState<SortingState>(DEFAULT_DELIVERY_SORTING)
|
||||
@@ -204,8 +208,6 @@ export const PlatformWebhooksEndpointDetails = ({
|
||||
<Card className="overflow-hidden">
|
||||
<CardContent className="pb-5">
|
||||
<dl className="grid grid-cols-1 gap-x-10 gap-y-6 md:grid-cols-2">
|
||||
{hasName && <DetailItem label="Name">{selectedEndpoint.name}</DetailItem>}
|
||||
|
||||
<DetailItem label="URL" ddClassName="flex items-start gap-2 text-sm">
|
||||
<span className="break-all">{selectedEndpoint.url}</span>
|
||||
<ShortcutTooltip
|
||||
|
||||
+1
-10
@@ -30,7 +30,6 @@ import { EmptyStatePresentational } from 'ui-patterns/EmptyStatePresentational'
|
||||
import { TimestampInfo } from 'ui-patterns/TimestampInfo'
|
||||
|
||||
import type { WebhookEndpoint } from './PlatformWebhooks.types'
|
||||
import { getWebhookEndpointDisplayName } from './PlatformWebhooks.utils'
|
||||
import { Shortcut } from '@/components/ui/Shortcut'
|
||||
import { onSearchInputEscape } from '@/lib/keyboard'
|
||||
import { createNavigationHandler } from '@/lib/navigation'
|
||||
@@ -170,9 +169,6 @@ export const PlatformWebhooksEndpointList = ({
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{sortedEndpoints.map((endpoint) => {
|
||||
const hasName = endpoint.name.trim().length > 0
|
||||
const displayName = getWebhookEndpointDisplayName(endpoint)
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
key={endpoint.id}
|
||||
@@ -197,12 +193,7 @@ export const PlatformWebhooksEndpointList = ({
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[420px]">
|
||||
<p className="truncate">{displayName}</p>
|
||||
{hasName && (
|
||||
<p className="text-xs text-foreground-lighter truncate mt-0.5">
|
||||
{endpoint.url}
|
||||
</p>
|
||||
)}
|
||||
<p className="truncate">{endpoint.url}</p>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[280px] truncate">
|
||||
{formatEventCount(endpoint.eventTypes)}
|
||||
|
||||
+2
-44
@@ -7,23 +7,10 @@ import type { WebhookEndpoint } from './PlatformWebhooks.types'
|
||||
import { PlatformWebhooksEndpointSheet, toEndpointPayload } from './PlatformWebhooksEndpointSheet'
|
||||
import { customRender } from '@/tests/lib/custom-render'
|
||||
|
||||
const { generateWebhookEndpointNameMock } = vi.hoisted(() => ({
|
||||
generateWebhookEndpointNameMock: vi.fn(() => 'winged-envelope'),
|
||||
}))
|
||||
|
||||
vi.mock(import('./PlatformWebhooks.utils'), async (importOriginal) => {
|
||||
const actual = await importOriginal()
|
||||
return {
|
||||
...actual,
|
||||
generateWebhookEndpointName: generateWebhookEndpointNameMock,
|
||||
}
|
||||
})
|
||||
|
||||
const PROJECT_EVENT_TYPES = ['project.updated', 'project.paused']
|
||||
|
||||
const createEndpoint = (overrides?: Partial<WebhookEndpoint>): WebhookEndpoint => ({
|
||||
id: '3c9b7e21-8d54-4f63-b2a1-6e7d8c9f0a12',
|
||||
name: 'Billing events',
|
||||
url: 'https://hooks.example.com/billing',
|
||||
description: 'Invoices and receipts',
|
||||
enabled: true,
|
||||
@@ -65,44 +52,16 @@ const findEventTypeCheckbox = (eventType: string) =>
|
||||
name: new RegExp(eventType.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
|
||||
})
|
||||
describe('PlatformWebhooksEndpointSheet', () => {
|
||||
it('prefills the name field when creating an endpoint', async () => {
|
||||
renderEndpointSheet()
|
||||
|
||||
expect(await screen.findByDisplayValue('winged-envelope')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('loads the existing name and description in edit mode', async () => {
|
||||
it('loads the existing url and description in edit mode', async () => {
|
||||
renderEndpointSheet({
|
||||
mode: 'edit',
|
||||
endpoint: createEndpoint(),
|
||||
})
|
||||
|
||||
expect(await screen.findByDisplayValue('Billing events')).toBeInTheDocument()
|
||||
expect(await screen.findByDisplayValue('https://hooks.example.com/billing')).toBeInTheDocument()
|
||||
expect(screen.getByDisplayValue('Invoices and receipts')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('submits an empty name when an existing name is cleared', async () => {
|
||||
const user = userEvent.setup()
|
||||
const { onSubmit } = renderEndpointSheet({
|
||||
mode: 'edit',
|
||||
endpoint: createEndpoint(),
|
||||
})
|
||||
|
||||
const nameInput = await screen.findByDisplayValue('Billing events')
|
||||
await user.clear(nameInput)
|
||||
submitForm()
|
||||
|
||||
await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1))
|
||||
expect(onSubmit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: '',
|
||||
url: 'https://hooks.example.com/billing',
|
||||
description: 'Invoices and receipts',
|
||||
}),
|
||||
expect.anything()
|
||||
)
|
||||
})
|
||||
|
||||
it('blocks submit when the endpoint URL is empty', async () => {
|
||||
const user = userEvent.setup()
|
||||
const { onSubmit } = renderEndpointSheet()
|
||||
@@ -248,7 +207,6 @@ describe('PlatformWebhooksEndpointSheet', () => {
|
||||
it('strips fully empty custom header rows from the payload', () => {
|
||||
expect(
|
||||
toEndpointPayload({
|
||||
name: 'Billing events',
|
||||
url: 'https://hooks.example.com/billing',
|
||||
description: '',
|
||||
enabled: true,
|
||||
|
||||
+3
-29
@@ -39,7 +39,7 @@ import type {
|
||||
WebhookEndpoint,
|
||||
WebhookScope,
|
||||
} from './PlatformWebhooks.types'
|
||||
import { generateSigningSecret, generateWebhookEndpointName } from './PlatformWebhooks.utils'
|
||||
import { generateSigningSecret } from './PlatformWebhooks.utils'
|
||||
import { DiscardChangesConfirmationDialog } from '@/components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog'
|
||||
import { InlineLink } from '@/components/ui/InlineLink'
|
||||
import { Shortcut } from '@/components/ui/Shortcut'
|
||||
@@ -50,7 +50,6 @@ import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
|
||||
const buildEndpointFormSchema = (mode: 'create' | 'edit') =>
|
||||
z
|
||||
.object({
|
||||
name: z.string().trim().max(64, 'Name cannot exceed 64 characters'),
|
||||
url: httpEndpointUrlSchema({
|
||||
requiredMessage: 'Please provide a URL',
|
||||
invalidMessage: 'Please provide a valid URL',
|
||||
@@ -153,7 +152,6 @@ const toControlId = (prefix: string, value: string) =>
|
||||
`${prefix}-${value.replace(/[^a-zA-Z0-9_-]/g, '-')}`
|
||||
|
||||
export const toEndpointPayload = (values: EndpointFormValues): UpsertWebhookEndpointInput => ({
|
||||
name: values.name,
|
||||
url: values.url,
|
||||
description: values.description,
|
||||
enabled: values.enabled,
|
||||
@@ -192,7 +190,6 @@ export const PlatformWebhooksEndpointSheet = ({
|
||||
const form = useForm<EndpointFormValues>({
|
||||
resolver: zodResolver(buildEndpointFormSchema(mode)),
|
||||
defaultValues: {
|
||||
name: generateWebhookEndpointName(),
|
||||
url: '',
|
||||
description: '',
|
||||
enabled: true,
|
||||
@@ -225,7 +222,6 @@ export const PlatformWebhooksEndpointSheet = ({
|
||||
|
||||
if (!endpoint) {
|
||||
form.reset({
|
||||
name: generateWebhookEndpointName(),
|
||||
url: '',
|
||||
description: '',
|
||||
enabled: true,
|
||||
@@ -238,7 +234,6 @@ export const PlatformWebhooksEndpointSheet = ({
|
||||
}
|
||||
|
||||
form.reset({
|
||||
name: endpoint.name,
|
||||
url: endpoint.url,
|
||||
description: endpoint.description,
|
||||
enabled: enabledOverride ?? endpoint.enabled,
|
||||
@@ -278,8 +273,8 @@ export const PlatformWebhooksEndpointSheet = ({
|
||||
<SheetTitle>{mode === 'create' ? 'Create endpoint' : 'Edit endpoint'}</SheetTitle>
|
||||
<SheetDescription className="sr-only">
|
||||
{mode === 'create'
|
||||
? 'Create a webhook endpoint by setting a name, URL, and event subscriptions.'
|
||||
: 'Edit this webhook endpoint name, URL, and event subscriptions.'}
|
||||
? 'Create a webhook endpoint by setting a URL and event subscriptions.'
|
||||
: 'Edit this webhook endpoint URL and event subscriptions.'}
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
<Separator />
|
||||
@@ -291,27 +286,6 @@ export const PlatformWebhooksEndpointSheet = ({
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="px-5 space-y-5">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
label={
|
||||
<>
|
||||
Name
|
||||
{/* Technically optional but encourage, so no (optional) label */}
|
||||
</>
|
||||
}
|
||||
layout="vertical"
|
||||
className="gap-1"
|
||||
>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="winged-envelope" maxLength={64} />
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="url"
|
||||
|
||||
@@ -20,6 +20,11 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
Label,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from 'ui'
|
||||
import { Admonition } from 'ui-patterns/Admonition'
|
||||
import { Input } from 'ui-patterns/DataInputs/Input'
|
||||
@@ -32,6 +37,7 @@ import {
|
||||
filterWebhookDeliveries,
|
||||
filterWebhookEndpoints,
|
||||
generateSigningSecret,
|
||||
getTestWebhookEventType,
|
||||
getWebhookEndpointDisplayName,
|
||||
} from './PlatformWebhooks.utils'
|
||||
import { PlatformWebhooksDeliveryDetailsSheet } from './PlatformWebhooksDeliveryDetailsSheet'
|
||||
@@ -120,6 +126,8 @@ export const PlatformWebhooksPage = ({ scope, endpointId }: PlatformWebhooksPage
|
||||
null
|
||||
)
|
||||
const [showRegenerateSecretConfirm, setShowRegenerateSecretConfirm] = useState(false)
|
||||
const [showTestEventPicker, setShowTestEventPicker] = useState(false)
|
||||
const [testEventType, setTestEventType] = useState('')
|
||||
const [editEnabledOverride, setEditEnabledOverride] = useState<boolean | null>(null)
|
||||
const [deliveryDetailsTab, setDeliveryDetailsTab] = useState<'event' | 'response'>('event')
|
||||
const [pendingCreatedEndpointId, setPendingCreatedEndpointId] = useState<string | null>(null)
|
||||
@@ -159,33 +167,22 @@ export const PlatformWebhooksPage = ({ scope, endpointId }: PlatformWebhooksPage
|
||||
)
|
||||
|
||||
const isEndpointView = !!selectedEndpoint
|
||||
const selectedEndpointHasName = selectedEndpoint ? selectedEndpoint.name.trim().length > 0 : false
|
||||
const selectedEndpointDisplayName = selectedEndpoint
|
||||
? getWebhookEndpointDisplayName(selectedEndpoint)
|
||||
: ''
|
||||
const headerTitle = isEndpointView ? selectedEndpointDisplayName : scopeLabel
|
||||
const headerDescription = isEndpointView
|
||||
? selectedEndpointHasName
|
||||
? (selectedEndpoint?.url ?? '')
|
||||
: ''
|
||||
: scopeDescription
|
||||
const headerDescription = isEndpointView ? '' : scopeDescription
|
||||
|
||||
const endpointPendingDelete = useMemo(
|
||||
() => endpoints.find((endpoint) => endpoint.id === endpointIdPendingDelete) ?? null,
|
||||
[endpoints, endpointIdPendingDelete]
|
||||
)
|
||||
const endpointPendingDeleteHasName = endpointPendingDelete
|
||||
? endpointPendingDelete.name.trim().length > 0
|
||||
: false
|
||||
const endpointPendingDeleteDisplayName = endpointPendingDelete
|
||||
? getWebhookEndpointDisplayName(endpointPendingDelete)
|
||||
: ''
|
||||
let deleteEndpointDescription = 'This action cannot be undone.'
|
||||
if (endpointPendingDelete) {
|
||||
deleteEndpointDescription = endpointPendingDeleteHasName
|
||||
? `Deleting “${endpointPendingDeleteDisplayName}” stops all deliveries to the URL below. This can’t be undone.`
|
||||
: 'Deleting this endpoint stops all deliveries to the URL below. This can’t be undone.'
|
||||
}
|
||||
const deleteEndpointDescription = endpointPendingDelete
|
||||
? `Deleting “${endpointPendingDeleteDisplayName}” stops all deliveries to the URL below. This can’t be undone.`
|
||||
: 'This action cannot be undone.'
|
||||
|
||||
const shouldRedirect = shouldRedirectFromWebhooks({
|
||||
areFlagsReady: isFeaturePreviewInitialized,
|
||||
@@ -360,10 +357,14 @@ export const PlatformWebhooksPage = ({ scope, endpointId }: PlatformWebhooksPage
|
||||
toast.success('Signing secret regenerated')
|
||||
}
|
||||
|
||||
const handleSendTestEvent = async () => {
|
||||
const handleSendTestEvent = async (eventType: string) => {
|
||||
if (!selectedEndpoint) return
|
||||
try {
|
||||
await testEndpointMutation.mutateAsync({ scope: apiScope, id: selectedEndpoint.id })
|
||||
await testEndpointMutation.mutateAsync({
|
||||
scope: apiScope,
|
||||
id: selectedEndpoint.id,
|
||||
eventType,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Failed to send webhook test event', error)
|
||||
return
|
||||
@@ -371,6 +372,23 @@ export const PlatformWebhooksPage = ({ scope, endpointId }: PlatformWebhooksPage
|
||||
toast.success('Test event sent — check deliveries shortly')
|
||||
}
|
||||
|
||||
const testEventTypeChoices = selectedEndpoint
|
||||
? selectedEndpoint.eventTypes.includes('*')
|
||||
? eventTypeOptions
|
||||
: selectedEndpoint.eventTypes
|
||||
: []
|
||||
|
||||
const handleSendTestEventClick = () => {
|
||||
if (!selectedEndpoint) return
|
||||
if (testEventTypeChoices.length <= 1) {
|
||||
const eventType = getTestWebhookEventType(selectedEndpoint.eventTypes, eventTypeOptions)
|
||||
if (eventType) handleSendTestEvent(eventType)
|
||||
return
|
||||
}
|
||||
setTestEventType(testEventTypeChoices[0])
|
||||
setShowTestEventPicker(true)
|
||||
}
|
||||
|
||||
const handleRetryDelivery = async (deliveryId: string) => {
|
||||
if (!selectedEndpoint) return
|
||||
const delivery = deliveries.find((item) => item.id === deliveryId)
|
||||
@@ -462,7 +480,7 @@ export const PlatformWebhooksPage = ({ scope, endpointId }: PlatformWebhooksPage
|
||||
<Button icon={<EllipsisVerticalIcon />} className="w-7" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" side="bottom" className="w-48">
|
||||
<DropdownMenuItem className="gap-x-2" onClick={handleSendTestEvent}>
|
||||
<DropdownMenuItem className="gap-x-2" onClick={handleSendTestEventClick}>
|
||||
<SendIcon size={14} className="text-foreground-lighter" />
|
||||
<span>Send test event</span>
|
||||
</DropdownMenuItem>
|
||||
@@ -574,6 +592,42 @@ export const PlatformWebhooksPage = ({ scope, endpointId }: PlatformWebhooksPage
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<AlertDialog open={showTestEventPicker} onOpenChange={setShowTestEventPicker}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Send test event</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Choose which subscribed event type to send as a test.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<div className="px-5 pb-5">
|
||||
<Select value={testEventType} onValueChange={setTestEventType}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{testEventTypeChoices.map((eventType) => (
|
||||
<SelectItem key={eventType} value={eventType}>
|
||||
{eventType}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => {
|
||||
setShowTestEventPicker(false)
|
||||
handleSendTestEvent(testEventType)
|
||||
}}
|
||||
>
|
||||
Send
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<AlertDialog open={showRegenerateSecretConfirm} onOpenChange={setShowRegenerateSecretConfirm}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
export { PlatformWebhooksPage } from './PlatformWebhooksPage'
|
||||
export {
|
||||
generateWebhookEndpointName,
|
||||
getWebhookEndpointDisplayName,
|
||||
} from './PlatformWebhooks.utils'
|
||||
export { getWebhookEndpointDisplayName } from './PlatformWebhooks.utils'
|
||||
export type {
|
||||
PlatformWebhooksState,
|
||||
UpsertWebhookEndpointInput,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
import { platformWebhooksKeys } from './keys'
|
||||
import { listWebhookDeliveries, type WebhookScopeParams } from './platform-webhooks-fetchers'
|
||||
import { toWebhookDeliverySummary } from './platform-webhooks-mappers'
|
||||
import { toWebhookDelivery } from './platform-webhooks-mappers'
|
||||
import type { WebhookDelivery } from '@/components/interfaces/Platform/Webhooks/PlatformWebhooks.types'
|
||||
import type { ResponseError, UseCustomQueryOptions } from '@/types'
|
||||
|
||||
@@ -19,7 +19,7 @@ async function getWebhookDeliveries(
|
||||
signal?: AbortSignal
|
||||
): Promise<WebhookDelivery[]> {
|
||||
const result = await listWebhookDeliveries(scope, endpointId, { size, after, before }, signal)
|
||||
return (result?.data ?? []).map((delivery) => toWebhookDeliverySummary(delivery, endpointId))
|
||||
return (result?.data ?? []).map((delivery) => toWebhookDelivery(delivery, endpointId))
|
||||
}
|
||||
|
||||
export type WebhookDeliveriesData = Awaited<ReturnType<typeof getWebhookDeliveries>>
|
||||
@@ -38,6 +38,7 @@ export const useWebhookDeliveriesQuery = <TData = WebhookDeliveriesData>(
|
||||
queryFn: ({ signal }) =>
|
||||
getWebhookDeliveries({ scope, endpointId, size, after, before }, signal),
|
||||
enabled: enabled && Boolean(endpointId),
|
||||
refetchInterval: 5000,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
@@ -28,10 +28,7 @@ async function createEndpoint({
|
||||
})
|
||||
if (!result?.data) throw new Error('Endpoint was created but no data was returned')
|
||||
|
||||
// The server never stores `name` (see PlatformWebhooks.types.ts) — carry through
|
||||
// what the user typed so it's visible for this session, even though it won't
|
||||
// survive a refetch or page reload.
|
||||
return { ...toWebhookEndpoint(result.data), name: input.name }
|
||||
return toWebhookEndpoint(result.data)
|
||||
}
|
||||
|
||||
type WebhookEndpointCreateData = Awaited<ReturnType<typeof createEndpoint>>
|
||||
|
||||
@@ -12,10 +12,11 @@ import type { ResponseError, UseCustomMutationOptions } from '@/types'
|
||||
export type WebhookEndpointTestVariables = {
|
||||
scope: WebhookScopeParams
|
||||
id: string
|
||||
eventType?: string
|
||||
}
|
||||
|
||||
async function testEndpoint({ scope, id }: WebhookEndpointTestVariables) {
|
||||
return sendTestWebhookEvent(scope, id)
|
||||
async function testEndpoint({ scope, id, eventType }: WebhookEndpointTestVariables) {
|
||||
return sendTestWebhookEvent(scope, id, eventType)
|
||||
}
|
||||
|
||||
type WebhookEndpointTestData = Awaited<ReturnType<typeof testEndpoint>>
|
||||
|
||||
@@ -29,9 +29,7 @@ async function updateEndpoint({
|
||||
})
|
||||
if (!result?.data) throw new Error('Endpoint was updated but no data was returned')
|
||||
|
||||
// See the comment in platform-webhook-endpoint-create-mutation.ts — `name` isn't
|
||||
// persisted by the server, so it's carried through from what the user submitted.
|
||||
return { ...toWebhookEndpoint(result.data), name: input.name }
|
||||
return toWebhookEndpoint(result.data)
|
||||
}
|
||||
|
||||
type WebhookEndpointUpdateData = Awaited<ReturnType<typeof updateEndpoint>>
|
||||
|
||||
@@ -30,7 +30,7 @@ export interface WebhookEndpointAttributesInput {
|
||||
description?: string | null
|
||||
enabled?: boolean
|
||||
event_types: { type: WebhookEventType }[]
|
||||
custom_headers?: Record<string, string> | null
|
||||
custom_headers?: Record<string, string>
|
||||
signing_secret?: string
|
||||
}
|
||||
|
||||
@@ -195,16 +195,29 @@ export async function getWebhookDelivery(
|
||||
return data
|
||||
}
|
||||
|
||||
export async function sendTestWebhookEvent(scope: WebhookScopeParams, id: string) {
|
||||
export async function sendTestWebhookEvent(
|
||||
scope: WebhookScopeParams,
|
||||
id: string,
|
||||
eventType?: string
|
||||
) {
|
||||
const body = eventType
|
||||
? {
|
||||
data: {
|
||||
type: 'event' as const,
|
||||
attributes: { type: eventType as Exclude<WebhookEventType, '*'> },
|
||||
},
|
||||
}
|
||||
: {}
|
||||
|
||||
const { data, error } =
|
||||
scope.scope === 'organization'
|
||||
? await post('/v2/organizations/{slug}/webhooks/endpoints/{id}/test', {
|
||||
params: { path: { slug: scope.orgSlug, id } },
|
||||
body: {},
|
||||
body,
|
||||
})
|
||||
: await post('/v2/projects/{ref}/webhooks/endpoints/{id}/test', {
|
||||
params: { path: { ref: scope.projectRef, id } },
|
||||
body: {},
|
||||
body,
|
||||
})
|
||||
|
||||
if (error) handleError(error)
|
||||
|
||||
@@ -26,8 +26,6 @@ const toCustomHeaders = (
|
||||
? Object.fromEntries(headers.map((header) => [header.key, header.value]))
|
||||
: undefined
|
||||
|
||||
// The real API has no `name` field on an endpoint — it is never sent here.
|
||||
// See the comment on `WebhookEndpoint.name` in PlatformWebhooks.types.ts.
|
||||
export const toWebhookEndpointAttributes = (
|
||||
input: UpsertWebhookEndpointInput
|
||||
): WebhookEndpointAttributesInput => ({
|
||||
@@ -42,20 +40,11 @@ export const toWebhookEndpointAttributes = (
|
||||
type WebhookEndpointResource =
|
||||
operations['v2-organizations-slug-webhooks-endpoints-id-get']['responses'][200]['content']['application/json']['data']
|
||||
|
||||
// The single-delivery endpoint — includes the full nested `event` (type + payload).
|
||||
type WebhookDeliveryResource =
|
||||
operations['v2-organizations-slug-webhooks-deliveries-id-get']['responses'][200]['content']['application/json']['data']
|
||||
|
||||
// The deliveries-list-for-an-endpoint endpoint — lighter weight, no `event` at all.
|
||||
type WebhookDeliveryListItemResource =
|
||||
operations['v2-organizations-slug-webhooks-endpoints-id-deliveries-get']['responses'][200]['content']['application/json']['data'][number]
|
||||
|
||||
// Response → UI direction. The real API never returns a `name` — `name` is left blank
|
||||
// here; until the backend adds a field for it, there's nowhere to read a saved name
|
||||
// back from. Flagged with Ali/Paweł as an open product question, not yet resolved.
|
||||
export const toWebhookEndpoint = (resource: WebhookEndpointResource): WebhookEndpoint => ({
|
||||
id: resource.id,
|
||||
name: '',
|
||||
url: resource.attributes.url,
|
||||
description: resource.attributes.description ?? '',
|
||||
enabled: resource.attributes.enabled,
|
||||
@@ -76,24 +65,8 @@ export const toWebhookDelivery = (
|
||||
id: resource.id,
|
||||
endpointId,
|
||||
eventId: resource.attributes.event_id,
|
||||
eventType: resource.attributes.event.type,
|
||||
eventPayload: resource.attributes.event.payload,
|
||||
status: resource.attributes.status,
|
||||
responseCode: resource.attributes.response_code,
|
||||
responseBody: resource.attributes.response_body,
|
||||
responseHeaders: resource.attributes.response_headers,
|
||||
attemptAt: resource.attributes.attempt_timestamp,
|
||||
})
|
||||
|
||||
// No `eventType` here — the list endpoint doesn't return it. See the comment on
|
||||
// `WebhookDelivery.eventType` in PlatformWebhooks.types.ts.
|
||||
export const toWebhookDeliverySummary = (
|
||||
resource: WebhookDeliveryListItemResource,
|
||||
endpointId: string
|
||||
): WebhookDelivery => ({
|
||||
id: resource.id,
|
||||
endpointId,
|
||||
eventId: resource.attributes.event_id,
|
||||
eventType: resource.attributes.event?.type ?? '',
|
||||
eventPayload: resource.attributes.event?.payload ?? {},
|
||||
status: resource.attributes.status,
|
||||
responseCode: resource.attributes.response_code,
|
||||
responseBody: resource.attributes.response_body,
|
||||
|
||||
Vendored
+113
-107
@@ -3056,7 +3056,7 @@ export interface operations {
|
||||
'application/json': components['schemas']['ErrorResponseBody']
|
||||
}
|
||||
}
|
||||
/** @description This feature requires the Enterprise organization plan. */
|
||||
/** @description This feature requires the Team, or Enterprise organization plan. */
|
||||
402: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -3127,7 +3127,7 @@ export interface operations {
|
||||
'application/json': components['schemas']['ErrorResponseBody']
|
||||
}
|
||||
}
|
||||
/** @description This feature requires the Enterprise organization plan. */
|
||||
/** @description This feature requires the Team, or Enterprise organization plan. */
|
||||
402: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -3189,7 +3189,7 @@ export interface operations {
|
||||
'application/json': components['schemas']['ErrorResponseBody']
|
||||
}
|
||||
}
|
||||
/** @description This feature requires the Enterprise organization plan. */
|
||||
/** @description This feature requires the Team, or Enterprise organization plan. */
|
||||
402: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -3354,6 +3354,8 @@ export interface operations {
|
||||
* @description Timestamp of the attempt.
|
||||
*/
|
||||
attempt_timestamp: string
|
||||
error_code: string | null
|
||||
error_message: string | null
|
||||
event: {
|
||||
/** @description Final data sent to the consumer. */
|
||||
payload: {
|
||||
@@ -3394,27 +3396,6 @@ export interface operations {
|
||||
| 'v1.organization.member.role.updated'
|
||||
| 'v1.organization.billing.plan.upgraded'
|
||||
| 'v1.organization.billing.plan.downgraded'
|
||||
| 'project.v1.paused'
|
||||
| 'project.v1.created'
|
||||
| 'project.v1.restored'
|
||||
| 'project.v1.transferred'
|
||||
| 'project.v1.removed'
|
||||
| 'project.v1.restarted'
|
||||
| 'project.v1.status.changed'
|
||||
| 'project.v1.backup.started'
|
||||
| 'project.v1.branch.created'
|
||||
| 'project.v1.branch.updated'
|
||||
| 'project.v1.branch.removed'
|
||||
| 'organization.v1.member.invitation.created'
|
||||
| 'organization.v1.member.invitation.canceled'
|
||||
| 'organization.v1.member.added'
|
||||
| 'organization.v1.member.removed'
|
||||
| 'organization.v1.member.role.assigned'
|
||||
| 'organization.v1.member.role.removed'
|
||||
| 'organization.v1.member.role.updated'
|
||||
| 'organization.v1.billing.plan.upgraded'
|
||||
| 'organization.v1.billing.plan.downgraded'
|
||||
| 'project.v1.branch.deleted'
|
||||
}
|
||||
/**
|
||||
* Format: uuid
|
||||
@@ -3439,7 +3420,7 @@ export interface operations {
|
||||
/** @description HTTP headers of the response, `{}` if unavailable. */
|
||||
response_headers: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/**
|
||||
* @description Status of the delivery attempt.
|
||||
* @enum {string}
|
||||
@@ -4036,7 +4017,7 @@ export interface operations {
|
||||
*/
|
||||
custom_headers?: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/** @description Optional description for the endpoint. */
|
||||
description: string | null
|
||||
/** @description Whether the endpoint is enabled or not - disabled endpoints won't emit any events. */
|
||||
@@ -4360,7 +4341,7 @@ export interface operations {
|
||||
*/
|
||||
custom_headers?: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/** @description Optional description for the endpoint. */
|
||||
description?: string | null
|
||||
/**
|
||||
@@ -4450,7 +4431,7 @@ export interface operations {
|
||||
*/
|
||||
custom_headers?: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/** @description Optional description for the endpoint. */
|
||||
description: string | null
|
||||
/** @description Whether the endpoint is enabled or not - disabled endpoints won't emit any events. */
|
||||
@@ -4768,7 +4749,7 @@ export interface operations {
|
||||
*/
|
||||
custom_headers?: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/** @description Optional description for the endpoint. */
|
||||
description: string | null
|
||||
/** @description Whether the endpoint is enabled or not - disabled endpoints won't emit any events. */
|
||||
@@ -5088,7 +5069,7 @@ export interface operations {
|
||||
*/
|
||||
custom_headers?: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/** @description Optional description for the endpoint. */
|
||||
description: string | null
|
||||
/** @description Whether the endpoint is enabled or not - disabled endpoints won't emit any events. */
|
||||
@@ -5445,7 +5426,7 @@ export interface operations {
|
||||
*/
|
||||
custom_headers?: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/** @description Optional description for the endpoint. */
|
||||
description: string | null
|
||||
/** @description Whether the endpoint is enabled or not - disabled endpoints won't emit any events. */
|
||||
@@ -5786,7 +5767,7 @@ export interface operations {
|
||||
*/
|
||||
custom_headers?: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/** @description Optional description for the endpoint. */
|
||||
description?: string | null
|
||||
/** @description Whether the endpoint is enabled or not - disabled endpoints won't emit any events. */
|
||||
@@ -5873,7 +5854,7 @@ export interface operations {
|
||||
*/
|
||||
custom_headers?: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/** @description Optional description for the endpoint. */
|
||||
description: string | null
|
||||
/** @description Whether the endpoint is enabled or not - disabled endpoints won't emit any events. */
|
||||
@@ -6224,6 +6205,49 @@ export interface operations {
|
||||
* @description Timestamp of the attempt.
|
||||
*/
|
||||
attempt_timestamp: string
|
||||
error_code: string | null
|
||||
error_message: string | null
|
||||
event: {
|
||||
/** @description Final data sent to the consumer. */
|
||||
payload: {
|
||||
/**
|
||||
* @description Organization slug
|
||||
* @example tsrqponmlkjihgfedcba
|
||||
*/
|
||||
organization_slug: string
|
||||
project_ref: string | null
|
||||
}
|
||||
/**
|
||||
* Format: date-time
|
||||
* @description Timestamp of event publication.
|
||||
*/
|
||||
timestamp: string
|
||||
/**
|
||||
* @description Webhook event type.
|
||||
* @enum {string}
|
||||
*/
|
||||
type:
|
||||
| 'v1.project.paused'
|
||||
| 'v1.project.created'
|
||||
| 'v1.project.restored'
|
||||
| 'v1.project.transferred'
|
||||
| 'v1.project.removed'
|
||||
| 'v1.project.restarted'
|
||||
| 'v1.project.status.changed'
|
||||
| 'v1.project.backup.started'
|
||||
| 'v1.project.branch.created'
|
||||
| 'v1.project.branch.updated'
|
||||
| 'v1.project.branch.removed'
|
||||
| 'v1.organization.member.invitation.created'
|
||||
| 'v1.organization.member.invitation.canceled'
|
||||
| 'v1.organization.member.added'
|
||||
| 'v1.organization.member.removed'
|
||||
| 'v1.organization.member.role.assigned'
|
||||
| 'v1.organization.member.role.removed'
|
||||
| 'v1.organization.member.role.updated'
|
||||
| 'v1.organization.billing.plan.upgraded'
|
||||
| 'v1.organization.billing.plan.downgraded'
|
||||
}
|
||||
/**
|
||||
* Format: uuid
|
||||
* @description Identifier of the event, which triggered the delivery (UUID v7).
|
||||
@@ -6247,7 +6271,7 @@ export interface operations {
|
||||
/** @description HTTP headers of the response, `{}` if unavailable. */
|
||||
response_headers: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/**
|
||||
* @description Status of the delivery attempt.
|
||||
* @enum {string}
|
||||
@@ -6581,27 +6605,6 @@ export interface operations {
|
||||
| 'v1.organization.member.role.updated'
|
||||
| 'v1.organization.billing.plan.upgraded'
|
||||
| 'v1.organization.billing.plan.downgraded'
|
||||
| 'project.v1.paused'
|
||||
| 'project.v1.created'
|
||||
| 'project.v1.restored'
|
||||
| 'project.v1.transferred'
|
||||
| 'project.v1.removed'
|
||||
| 'project.v1.restarted'
|
||||
| 'project.v1.status.changed'
|
||||
| 'project.v1.backup.started'
|
||||
| 'project.v1.branch.created'
|
||||
| 'project.v1.branch.updated'
|
||||
| 'project.v1.branch.removed'
|
||||
| 'organization.v1.member.invitation.created'
|
||||
| 'organization.v1.member.invitation.canceled'
|
||||
| 'organization.v1.member.added'
|
||||
| 'organization.v1.member.removed'
|
||||
| 'organization.v1.member.role.assigned'
|
||||
| 'organization.v1.member.role.removed'
|
||||
| 'organization.v1.member.role.updated'
|
||||
| 'organization.v1.billing.plan.upgraded'
|
||||
| 'organization.v1.billing.plan.downgraded'
|
||||
| 'project.v1.branch.deleted'
|
||||
}
|
||||
/**
|
||||
* @description Resource type.
|
||||
@@ -8359,6 +8362,8 @@ export interface operations {
|
||||
* @description Timestamp of the attempt.
|
||||
*/
|
||||
attempt_timestamp: string
|
||||
error_code: string | null
|
||||
error_message: string | null
|
||||
event: {
|
||||
/** @description Final data sent to the consumer. */
|
||||
payload: {
|
||||
@@ -8399,27 +8404,6 @@ export interface operations {
|
||||
| 'v1.organization.member.role.updated'
|
||||
| 'v1.organization.billing.plan.upgraded'
|
||||
| 'v1.organization.billing.plan.downgraded'
|
||||
| 'project.v1.paused'
|
||||
| 'project.v1.created'
|
||||
| 'project.v1.restored'
|
||||
| 'project.v1.transferred'
|
||||
| 'project.v1.removed'
|
||||
| 'project.v1.restarted'
|
||||
| 'project.v1.status.changed'
|
||||
| 'project.v1.backup.started'
|
||||
| 'project.v1.branch.created'
|
||||
| 'project.v1.branch.updated'
|
||||
| 'project.v1.branch.removed'
|
||||
| 'organization.v1.member.invitation.created'
|
||||
| 'organization.v1.member.invitation.canceled'
|
||||
| 'organization.v1.member.added'
|
||||
| 'organization.v1.member.removed'
|
||||
| 'organization.v1.member.role.assigned'
|
||||
| 'organization.v1.member.role.removed'
|
||||
| 'organization.v1.member.role.updated'
|
||||
| 'organization.v1.billing.plan.upgraded'
|
||||
| 'organization.v1.billing.plan.downgraded'
|
||||
| 'project.v1.branch.deleted'
|
||||
}
|
||||
/**
|
||||
* Format: uuid
|
||||
@@ -8444,7 +8428,7 @@ export interface operations {
|
||||
/** @description HTTP headers of the response, `{}` if unavailable. */
|
||||
response_headers: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/**
|
||||
* @description Status of the delivery attempt.
|
||||
* @enum {string}
|
||||
@@ -9041,7 +9025,7 @@ export interface operations {
|
||||
*/
|
||||
custom_headers?: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/** @description Optional description for the endpoint. */
|
||||
description: string | null
|
||||
/** @description Whether the endpoint is enabled or not - disabled endpoints won't emit any events. */
|
||||
@@ -9365,7 +9349,7 @@ export interface operations {
|
||||
*/
|
||||
custom_headers?: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/** @description Optional description for the endpoint. */
|
||||
description?: string | null
|
||||
/**
|
||||
@@ -9455,7 +9439,7 @@ export interface operations {
|
||||
*/
|
||||
custom_headers?: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/** @description Optional description for the endpoint. */
|
||||
description: string | null
|
||||
/** @description Whether the endpoint is enabled or not - disabled endpoints won't emit any events. */
|
||||
@@ -9773,7 +9757,7 @@ export interface operations {
|
||||
*/
|
||||
custom_headers?: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/** @description Optional description for the endpoint. */
|
||||
description: string | null
|
||||
/** @description Whether the endpoint is enabled or not - disabled endpoints won't emit any events. */
|
||||
@@ -10093,7 +10077,7 @@ export interface operations {
|
||||
*/
|
||||
custom_headers?: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/** @description Optional description for the endpoint. */
|
||||
description: string | null
|
||||
/** @description Whether the endpoint is enabled or not - disabled endpoints won't emit any events. */
|
||||
@@ -10450,7 +10434,7 @@ export interface operations {
|
||||
*/
|
||||
custom_headers?: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/** @description Optional description for the endpoint. */
|
||||
description: string | null
|
||||
/** @description Whether the endpoint is enabled or not - disabled endpoints won't emit any events. */
|
||||
@@ -10791,7 +10775,7 @@ export interface operations {
|
||||
*/
|
||||
custom_headers?: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/** @description Optional description for the endpoint. */
|
||||
description?: string | null
|
||||
/** @description Whether the endpoint is enabled or not - disabled endpoints won't emit any events. */
|
||||
@@ -10878,7 +10862,7 @@ export interface operations {
|
||||
*/
|
||||
custom_headers?: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/** @description Optional description for the endpoint. */
|
||||
description: string | null
|
||||
/** @description Whether the endpoint is enabled or not - disabled endpoints won't emit any events. */
|
||||
@@ -11229,6 +11213,49 @@ export interface operations {
|
||||
* @description Timestamp of the attempt.
|
||||
*/
|
||||
attempt_timestamp: string
|
||||
error_code: string | null
|
||||
error_message: string | null
|
||||
event: {
|
||||
/** @description Final data sent to the consumer. */
|
||||
payload: {
|
||||
/**
|
||||
* @description Organization slug
|
||||
* @example tsrqponmlkjihgfedcba
|
||||
*/
|
||||
organization_slug: string
|
||||
project_ref: string | null
|
||||
}
|
||||
/**
|
||||
* Format: date-time
|
||||
* @description Timestamp of event publication.
|
||||
*/
|
||||
timestamp: string
|
||||
/**
|
||||
* @description Webhook event type.
|
||||
* @enum {string}
|
||||
*/
|
||||
type:
|
||||
| 'v1.project.paused'
|
||||
| 'v1.project.created'
|
||||
| 'v1.project.restored'
|
||||
| 'v1.project.transferred'
|
||||
| 'v1.project.removed'
|
||||
| 'v1.project.restarted'
|
||||
| 'v1.project.status.changed'
|
||||
| 'v1.project.backup.started'
|
||||
| 'v1.project.branch.created'
|
||||
| 'v1.project.branch.updated'
|
||||
| 'v1.project.branch.removed'
|
||||
| 'v1.organization.member.invitation.created'
|
||||
| 'v1.organization.member.invitation.canceled'
|
||||
| 'v1.organization.member.added'
|
||||
| 'v1.organization.member.removed'
|
||||
| 'v1.organization.member.role.assigned'
|
||||
| 'v1.organization.member.role.removed'
|
||||
| 'v1.organization.member.role.updated'
|
||||
| 'v1.organization.billing.plan.upgraded'
|
||||
| 'v1.organization.billing.plan.downgraded'
|
||||
}
|
||||
/**
|
||||
* Format: uuid
|
||||
* @description Identifier of the event, which triggered the delivery (UUID v7).
|
||||
@@ -11252,7 +11279,7 @@ export interface operations {
|
||||
/** @description HTTP headers of the response, `{}` if unavailable. */
|
||||
response_headers: {
|
||||
[key: string]: string
|
||||
} | null
|
||||
}
|
||||
/**
|
||||
* @description Status of the delivery attempt.
|
||||
* @enum {string}
|
||||
@@ -11586,27 +11613,6 @@ export interface operations {
|
||||
| 'v1.organization.member.role.updated'
|
||||
| 'v1.organization.billing.plan.upgraded'
|
||||
| 'v1.organization.billing.plan.downgraded'
|
||||
| 'project.v1.paused'
|
||||
| 'project.v1.created'
|
||||
| 'project.v1.restored'
|
||||
| 'project.v1.transferred'
|
||||
| 'project.v1.removed'
|
||||
| 'project.v1.restarted'
|
||||
| 'project.v1.status.changed'
|
||||
| 'project.v1.backup.started'
|
||||
| 'project.v1.branch.created'
|
||||
| 'project.v1.branch.updated'
|
||||
| 'project.v1.branch.removed'
|
||||
| 'organization.v1.member.invitation.created'
|
||||
| 'organization.v1.member.invitation.canceled'
|
||||
| 'organization.v1.member.added'
|
||||
| 'organization.v1.member.removed'
|
||||
| 'organization.v1.member.role.assigned'
|
||||
| 'organization.v1.member.role.removed'
|
||||
| 'organization.v1.member.role.updated'
|
||||
| 'organization.v1.billing.plan.upgraded'
|
||||
| 'organization.v1.billing.plan.downgraded'
|
||||
| 'project.v1.branch.deleted'
|
||||
}
|
||||
/**
|
||||
* @description Resource type.
|
||||
|
||||
+143
@@ -435,6 +435,24 @@ export interface paths {
|
||||
patch?: never
|
||||
trace?: never
|
||||
}
|
||||
'/platform/database/{ref}/db-integrity/operations/{op}': {
|
||||
parameters: {
|
||||
query?: never
|
||||
header?: never
|
||||
path?: never
|
||||
cookie?: never
|
||||
}
|
||||
/** Reads the current db-integrity task for an operation on the project primary. */
|
||||
get: operations['DatabaseDbIntegrityController_status']
|
||||
put?: never
|
||||
/** Submits a db-integrity operation on the project primary. */
|
||||
post: operations['DatabaseDbIntegrityController_submit']
|
||||
delete?: never
|
||||
options?: never
|
||||
head?: never
|
||||
patch?: never
|
||||
trace?: never
|
||||
}
|
||||
'/platform/database/{ref}/hook-enable': {
|
||||
parameters: {
|
||||
query?: never
|
||||
@@ -6729,6 +6747,8 @@ export interface components {
|
||||
| 'audit_logs_read'
|
||||
| 'billing_read'
|
||||
| 'billing_write'
|
||||
| 'organization_config_read'
|
||||
| 'organization_config_write'
|
||||
| 'members_read'
|
||||
| 'members_write'
|
||||
| 'organization_projects_read'
|
||||
@@ -6749,8 +6769,12 @@ export interface components {
|
||||
| 'api_gateway_keys_write'
|
||||
| 'auth_config_read'
|
||||
| 'auth_config_write'
|
||||
| 'auth_hooks_read'
|
||||
| 'auth_hooks_write'
|
||||
| 'auth_signing_keys_read'
|
||||
| 'auth_signing_keys_write'
|
||||
| 'auth_users_read'
|
||||
| 'auth_users_write'
|
||||
| 'backups_read'
|
||||
| 'backups_write'
|
||||
| 'branching_development_create'
|
||||
@@ -6852,6 +6876,7 @@ export interface components {
|
||||
| 't4g.nano'
|
||||
| 't3a.nano'
|
||||
| 't3.nano'
|
||||
| 't8i.nano'
|
||||
| 't4g.micro'
|
||||
| 't3a.micro'
|
||||
| 't3.micro'
|
||||
@@ -7693,6 +7718,8 @@ export interface components {
|
||||
| 'audit_logs_read'
|
||||
| 'billing_read'
|
||||
| 'billing_write'
|
||||
| 'organization_config_read'
|
||||
| 'organization_config_write'
|
||||
| 'members_read'
|
||||
| 'members_write'
|
||||
| 'organization_projects_read'
|
||||
@@ -7713,8 +7740,12 @@ export interface components {
|
||||
| 'api_gateway_keys_write'
|
||||
| 'auth_config_read'
|
||||
| 'auth_config_write'
|
||||
| 'auth_hooks_read'
|
||||
| 'auth_hooks_write'
|
||||
| 'auth_signing_keys_read'
|
||||
| 'auth_signing_keys_write'
|
||||
| 'auth_users_read'
|
||||
| 'auth_users_write'
|
||||
| 'backups_read'
|
||||
| 'backups_write'
|
||||
| 'branching_development_create'
|
||||
@@ -8075,6 +8106,22 @@ export interface components {
|
||||
| 'RESTARTING'
|
||||
| 'RESIZING'
|
||||
}
|
||||
DbIntegritySubmitBody: {
|
||||
idempotency_key?: string
|
||||
metadata?: {
|
||||
[key: string]: string
|
||||
}
|
||||
parameters?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
rerun?: boolean
|
||||
}
|
||||
DbIntegrityTaskResponse_Output: {
|
||||
error?: string
|
||||
host_lock_yield?: unknown
|
||||
result?: unknown
|
||||
status: string
|
||||
}
|
||||
DeclineAuthorizationResponse_Output: {
|
||||
id: string
|
||||
}
|
||||
@@ -14311,6 +14358,8 @@ export interface components {
|
||||
| 'audit_logs_read'
|
||||
| 'billing_read'
|
||||
| 'billing_write'
|
||||
| 'organization_config_read'
|
||||
| 'organization_config_write'
|
||||
| 'members_read'
|
||||
| 'members_write'
|
||||
| 'organization_projects_read'
|
||||
@@ -14331,8 +14380,12 @@ export interface components {
|
||||
| 'api_gateway_keys_write'
|
||||
| 'auth_config_read'
|
||||
| 'auth_config_write'
|
||||
| 'auth_hooks_read'
|
||||
| 'auth_hooks_write'
|
||||
| 'auth_signing_keys_read'
|
||||
| 'auth_signing_keys_write'
|
||||
| 'auth_users_read'
|
||||
| 'auth_users_write'
|
||||
| 'backups_read'
|
||||
| 'backups_write'
|
||||
| 'branching_development_create'
|
||||
@@ -17841,6 +17894,96 @@ export interface operations {
|
||||
}
|
||||
}
|
||||
}
|
||||
DatabaseDbIntegrityController_status: {
|
||||
parameters: {
|
||||
query?: never
|
||||
header?: never
|
||||
path: {
|
||||
op: 'collation' | 'collation_check'
|
||||
ref: string
|
||||
}
|
||||
cookie?: never
|
||||
}
|
||||
requestBody?: never
|
||||
responses: {
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content: {
|
||||
'application/json': components['schemas']['DbIntegrityTaskResponse_Output']
|
||||
}
|
||||
}
|
||||
/** @description Unauthorized */
|
||||
401: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
/** @description Forbidden action */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
/** @description Rate limit exceeded */
|
||||
429: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
}
|
||||
}
|
||||
DatabaseDbIntegrityController_submit: {
|
||||
parameters: {
|
||||
query?: never
|
||||
header?: never
|
||||
path: {
|
||||
op: 'collation' | 'collation_check'
|
||||
ref: string
|
||||
}
|
||||
cookie?: never
|
||||
}
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': components['schemas']['DbIntegritySubmitBody']
|
||||
}
|
||||
}
|
||||
responses: {
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content: {
|
||||
'application/json': components['schemas']['DbIntegrityTaskResponse_Output']
|
||||
}
|
||||
}
|
||||
/** @description Unauthorized */
|
||||
401: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
/** @description Forbidden action */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
/** @description Rate limit exceeded */
|
||||
429: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
}
|
||||
}
|
||||
HooksController_enableHooks: {
|
||||
parameters: {
|
||||
query?: never
|
||||
|
||||
Reference in new issue
Block a user