import { ActionList, ActionMenu, Flash } from '@primer/react' import { useState, KeyboardEvent, useEffect } from 'react' import useSWR from 'swr' import { useRouter } from 'next/router' import { slug } from 'github-slugger' import cx from 'classnames' import { useMainContext } from 'components/context/MainContext' import { useVersion } from 'components/hooks/useVersion' import { LinkIconHeading } from 'components/article/LinkIconHeading' import { useTranslation } from 'components/hooks/useTranslation' import type { WebhookAction, WebhookData } from './types' import { ParameterTable } from 'components/parameter-table/ParameterTable' import styles from './WebhookPayloadExample.module.scss' type Props = { webhook: WebhookAction } // fetcher passed to useSWR() to get webhook data using the given URL async function webhookFetcher(url: string) { const response = await fetch(url) if (!response.ok) { throw new Error(`${response.status} on ${url}`) } return response.json() } // We manually created decorated webhooks files for GHES versions older than // 3.7, returns whether the given version is one of these versions of GHES. // // TODO: once 3.7 is the oldest supported version of GHES, we won't need this // anymore. function isScrapedGhesVersion(version: ReturnType) { const scrapedVersions = ['3.6', '3.5', '3.4', '3.3', '3.2'] if (!version.isEnterprise) return false // getting the number part e.g. '3.6' from a version string like // 'enterprise-server@3.6' const versionNumber = version.currentVersion.split('@')[1] return scrapedVersions.includes(versionNumber) } export function Webhook({ webhook }: Props) { // Get version for requests to switch webhook action type const version = useVersion() const { t } = useTranslation('products') const router = useRouter() const { locale } = router const context = useMainContext() // Get more user friendly language for the different availability options in // the webhook schema (we can't change it directly in the schema). Note that // we specifically don't want to translate these strings with useTranslation() // like we usually do with strings from data/ui.yml. const rephraseAvailability = context.data.ui.products.webhooks.rephrase_availability // The param that was clicked so we can expand its property
element const [clickedBodyParameterName, setClickedBodyParameterName] = useState('') // The selected webhook action type the user selects via a dropdown const [selectedWebhookActionType, setSelectedWebhookActionType] = useState('') // The index of the selected action type so we can highlight which one is selected // in the action type dropdown const [selectedActionTypeIndex, setSelectedActionTypeIndex] = useState(0) const webhookSlug = slug(webhook.data.category) const webhookFetchUrl = `/api/webhooks/v1?${new URLSearchParams({ category: webhook.data.category, version: version.currentVersion, })}` // When you load the page we want to support linking to a specific webhook type // so this effect sets the webhook type if it's provided in the URL e.g.: // // webhook-events-and-payloads?actionType=published#package // // where the webhook is set in the hash (which is equal to webhookSlug) and // the webhook action type is passed in the actionType parameter. useEffect(() => { const url = new URL(location.href) const actionType = url.searchParams.get('actionType') const hash = url.hash?.slice(1) if (actionType && hash && webhook.actionTypes.includes(actionType) && hash === webhookSlug) { setSelectedWebhookActionType(actionType) setSelectedActionTypeIndex(webhook.actionTypes.indexOf(actionType)) } }, []) // callback for the action type dropdown -- sets the action type to the given // type, index is the index of the selected type so we can highlight it as // selected. // // Besides setting the action type state, we also want to: // // * clear the clicked body param so that no properties are expanded when we // re-render the webhook // * update the URL so people can link to a specific webhook action type function handleActionTypeChange(type: string, index: number) { setClickedBodyParameterName('') setSelectedWebhookActionType(type) setSelectedActionTypeIndex(index) const { asPath } = router let [pathRoot, pathQuery = ''] = asPath.split('?') const params = new URLSearchParams(pathQuery) if (pathRoot.includes('#')) { pathRoot = pathRoot.split('#')[0] } params.set('actionType', type) router.push({ pathname: pathRoot, query: params.toString(), hash: webhookSlug }, undefined, { shallow: true, locale, }) } // callback to trigger useSWR() hook after a nested property is clicked function handleBodyParamExpansion(event: KeyboardEvent) { // need to cast it because 'closest' isn't necessarily available on // event.target const target = event.target as HTMLElement setClickedBodyParameterName(target.closest('details')?.dataset.nestedParamId) } // fires when the webhook action type changes or someone clicks on a nested // body param for the first time. In either case, we now have all the data // for a webhook (i.e. all the data for each action type and all of their // nested parameters) const { data, error } = useSWR( clickedBodyParameterName || selectedWebhookActionType ? webhookFetchUrl : null, webhookFetcher, { revalidateOnFocus: false, } ) const currentWebhookActionType = selectedWebhookActionType || webhook.data.action const currentWebhookAction = (data && data[currentWebhookActionType]) || webhook.data return (

{currentWebhookAction.category}

{t('webhooks.availability')}

    {currentWebhookAction.availability.map((availability) => { // TODO: once 3.7 is the oldest supported version of GHES, we won't need this anymore. if (isScrapedGhesVersion(version)) { return (
  • ) } else { return (
  • {rephraseAvailability[availability] ?? availability}
  • ) } })}

{t('webhooks.webhook_payload_object')}

{error && (

{t('webhooks.action_type_switch_error')}

{error.toString()}

)} {webhook.actionTypes.length > 1 && (

{t('webhooks.action_type')}

{currentWebhookActionType} {webhook.actionTypes.map((type, index) => { return ( handleActionTypeChange(type, index)} > {type} ) })}
)}
{webhook.data.payloadExample && ( <>

{t('webhooks.webhook_payload_example')}

{JSON.stringify(webhook.data.payloadExample, null, 2)}
)}
) }