diff --git a/lib/webhooks/README.md b/lib/webhooks/README.md new file mode 100644 index 0000000000..07693be0d8 --- /dev/null +++ b/lib/webhooks/README.md @@ -0,0 +1,44 @@ +# Webhooks + +## About this directory + +* `lib/webhooks/index.js` is human-editable. +* `lib/rest/static/**/*.payload.json` are manually edited and copied. When a new GHES release is created, the static webhook files from the previous version's directory are copied to a new version directory. + +## Editable files + +* `lib/webhooks/index.js` consumes the static JSON files in `lib/webhooks/static` and exports the data used by the REST middleware contextualizer. + +## Static files + +Generated by `script/rest/update-files.js`: + +* `lib/rest/static/dereferenced` - dereferenced OpenAPI schema file for each version of GitHub +* `lib/rest/static/decorated` - files generated from the dereferenced OpenAPI schema with the Markdown descriptions rendered in HTML + +## Rendering docs + +When the server starts, `middleware/contextualizers/webhooks.js` accesses the data exported from the static webhook JSON files, fetches the data for the current version and requested path, and adds it to the `context` object. The added property is: + +* `req.context.webhookPayloadsForCurrentVersion` - all webhook payloads with a version matching the current version + +Markdown files in `content/developers/webhooks-and-events/webhooks/webhook-events-and-payloads.md` use Liquid to display the webhook payloads in `req.context.webhookPayloadsForCurrentVersion`. For example `{{ webhookPayloadsForCurrentVersion.user.created }}` references the payload file `user.created.payload.json` for the version being viewed. + +**Note** Payload files either contain the webhook action type or no action type at all. For example, `user.created.payload.json` is the webhook `user` with the action type of `created`. Not all webhooks have action types. If a file exists with no action type (e.g., `user.payload.json`) and the action types (e.g., `user.created.payload.json` and `user.deleted.payload.json`), the entry in the context for the file with no action type will be `default`. For example, for the three static file mentioned, the object would be: + +``` +{ + user: { + default: "STRING VALUE", + created: "STRING VALUE", + deleted: "STRING VALUE" + } +} +``` + +If no action types exist, and only `user.payload.json` exists, the object would be: + +``` +{ + user: "STRING VALUE" +} diff --git a/lib/webhooks/index.js b/lib/webhooks/index.js index f5a24bdc96..58f27065ac 100644 --- a/lib/webhooks/index.js +++ b/lib/webhooks/index.js @@ -19,7 +19,12 @@ versions.forEach((version) => { walk(versionSubdir, { includeBasePath: true }).forEach((payloadFile) => { // payload file: /path/to/check_run.completed.payload.json // payload path: check_run.completed - const payloadPath = path.basename(payloadFile).replace('.payload.json', '') + let payloadPath = path.basename(payloadFile).replace('.payload.json', '') + if (!payloadPath.includes('.') && payloadsPerVersion[payloadPath]) { + // append the key `default` to the payload path to + // prevent overwriting existing object + payloadPath = `${payloadPath}.default` + } set( payloadsPerVersion, payloadPath, diff --git a/tests/rendering/webhooks.js b/tests/rendering/webhooks.js new file mode 100644 index 0000000000..af6fff4a9e --- /dev/null +++ b/tests/rendering/webhooks.js @@ -0,0 +1,32 @@ +import { jest } from '@jest/globals' +import { getDOM } from '../helpers/supertest.js' +import { allVersions } from '../../lib/all-versions.js' + +describe('webhooks events and payloads', () => { + jest.setTimeout(300 * 1000) + + describe('rendering', () => { + test('every webhook event has at least one payload example', async () => { + const versions = Object.values(allVersions).map((value) => value.version) + + // For all versions, check that the webhook events and payloads page + // has at least one payload example for each event. Payload examples + // start with the id `webhook-payload-example` and have a sibling div + // with the class `height-constrained-code-block`. The sibling is + // usually but not always the next sibling element, which is why + // `nextUntil` is used. + for (const version of versions) { + const page = `/${version}/developers/webhooks-and-events/webhooks/webhook-events-and-payloads` + const $ = await getDOM(page) + const payloadExampleElem = $('[id^=webhook-payload-example]') + + payloadExampleElem.each((i, elem) => { + const siblings = $(elem) + .nextUntil('[id^=webhook-payload-example]') + .filter((i, elem) => $(elem).hasClass('height-constrained-code-block')) + expect(siblings.length).toBeGreaterThan(0) + }) + } + }) + }) +})