Merge pull request #179 from github/joshmgross/deduplicate-webhook-objects

Deduplicate objects in webhook payloads
This commit is contained in:
Josh Gross
2023-03-14 13:08:04 -04:00
committed by GitHub
5 changed files with 27790 additions and 387156 deletions
@@ -0,0 +1,93 @@
import Webhook from "./webhook";
// Store any repeated body parameters in an array
// and replace them in the webhook with an index in the array
export function deduplicateWebhooks(webhooks: Record<string, Record<string, Webhook>>): any[] {
/**
* Build a map of all objects by name
*/
const objectsByName: Record<string, any[]> = {};
const objectCount: Record<string, number> = {};
for (const webhook of iterateWebhooks(webhooks)) {
for (const param of webhook.bodyParameters) {
objectsByName[param.name] ||= [];
const index = findOrAdd(param, objectsByName[param.name]);
const key = `${param.name}:${index}`;
objectCount[key] ||= 0;
objectCount[key]++;
}
}
/**
* Replace any duplicated body parameters with an index
*/
const duplicatedBodyParams: any[] = [];
const bodyParamIndexMap: Record<string, number> = {};
for (const webhook of iterateWebhooks(webhooks)) {
const newParams: any[] = [];
for (const param of webhook.bodyParameters) {
const index = find(param, objectsByName[param.name]);
const key = `${param.name}:${index}`;
if (objectCount[key] > 1) {
newParams.push(indexForParam(param, index, bodyParamIndexMap, duplicatedBodyParams));
} else {
// If an object is only used once, keep it inline
newParams.push(param);
}
}
webhook.bodyParameters = newParams;
}
return duplicatedBodyParams;
}
function* iterateWebhooks(webhooks: Record<string, Record<string, Webhook>>) {
for (const webhookActions of Object.values(webhooks)) {
for (const webhook of Object.values(webhookActions)) {
yield webhook;
}
}
}
function findOrAdd(param: any, objects: any[]): number {
for (const [index, object] of objects.entries()) {
if (JSON.stringify(object) === JSON.stringify(param)) {
return index;
}
}
return objects.push(param) - 1;
}
function find(param: any, objects: any[]): number {
for (const [index, object] of objects.entries()) {
if (JSON.stringify(object) === JSON.stringify(param)) {
return index;
}
}
throw new Error(`Could not find object ${param.name}`);
}
function indexForParam(
param: any,
paramNameIndex: number,
objectIndexMap: Record<string, number>,
duplicatedBodyParams: any[]
): number {
const key = `${param.name}:${paramNameIndex}`;
const existingIndex = objectIndexMap[key];
if (existingIndex !== undefined) {
// Object has already been added to the array
return existingIndex;
}
// Add the object to the array
const objIdx = duplicatedBodyParams.push(param) - 1;
objectIndexMap[key] = objIdx;
return objIdx;
}
+5
View File
@@ -2,9 +2,11 @@ import {promises as fs} from "fs";
import Webhook from "./webhook.js";
import schemaImport from "rest-api-description/descriptions/api.github.com/dereferenced/api.github.com.deref.json" assert {type: "json"};
import {deduplicateWebhooks} from "./deduplicate.js";
const schema = schemaImport as any;
const OUTPUT_PATH = "./src/context-providers/events/webhooks.json";
const OBJECTS_PATH = "./src/context-providers/events/objects.json";
const rawWebhooks = Object.values(schema.webhooks || schema["x-webhooks"]) as any[];
if (!rawWebhooks) {
@@ -31,4 +33,7 @@ for (const webhook of webhooks) {
}
}
const objectsArray = deduplicateWebhooks(categorizedWebhooks);
await fs.writeFile(OBJECTS_PATH, JSON.stringify(objectsArray, null, 2));
await fs.writeFile(OUTPUT_PATH, JSON.stringify(categorizedWebhooks, null, 2));
@@ -1,6 +1,7 @@
import {data, DescriptionDictionary} from "@github/actions-expressions";
import webhooks from "./webhooks.json";
import webhookObjects from "./objects.json";
import schedule from "./schedule.json" assert {type: "json"};
import workflow_call from "./workflow_call.json" assert {type: "json"};
@@ -47,17 +48,38 @@ type Param = {
enum?: string[];
};
/**
* A full {@link Param} or an index into the objects array for deduplicated parameters
*/
type DeduplicatedParam = Param | number;
type WebhookPayload = {
descriptionHtml: string;
summaryHtml: string;
bodyParameters: Param[];
};
type Webhooks = {
[name: string]: {
[action: string]: {
descriptionHtml: string;
summaryHtml: string;
bodyParameters: Param[];
[action: string]: WebhookPayload;
};
};
type DeduplicatedWebhooks = {
[name: string]: {
[action: string]: WebhookPayload & {
bodyParameters: DeduplicatedParam[];
};
};
};
const webhookPayloads: Webhooks = webhooks as any;
const dedupedWebhookPayloads: DeduplicatedWebhooks = webhooks as any;
const objects: Param[] = webhookObjects as any;
const webhookPayloads: Webhooks = {};
// Hydrate the workflow dispatch payload if it exists
getWebhookPayload("workflow_dispatch", "default");
//
// Manual work-arounds for webhook issues
@@ -68,7 +90,7 @@ if (inputs) {
}
export function getEventPayload(event: string, action: string = "default"): DescriptionDictionary | undefined {
const payload = webhookPayloads?.[event]?.[action];
const payload = getWebhookPayload(event, action);
if (!payload) {
// Not all events are real webhooks. Check if there is a custom payload for this event
const customPayload = customEventPayloads[event];
@@ -120,3 +142,36 @@ function mergeObject(d: DescriptionDictionary, toAdd: Object): DescriptionDictio
return d;
}
function getWebhookPayload(event: string, action: string): WebhookPayload | undefined {
const deduplicatedPayload = dedupedWebhookPayloads?.[event]?.[action];
if (!deduplicatedPayload) {
return undefined;
}
const existingPayload = webhookPayloads?.[event]?.[action];
if (existingPayload) {
return existingPayload;
}
// Recreate the full payload and store it for reuse
const params = deduplicatedPayload.bodyParameters.map(p => fullParam(p));
const payload = {
...deduplicatedPayload,
bodyParameters: params
};
webhookPayloads[event] ||= {};
webhookPayloads[event][action] = payload;
return payload;
}
function fullParam(dedupedParam: DeduplicatedParam): Param {
if (typeof dedupedParam === "number") {
if (dedupedParam >= objects.length) {
throw new Error(`Unknown object ${dedupedParam}`);
}
return objects[dedupedParam];
}
return dedupedParam;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff