Merge pull request #179 from github/joshmgross/deduplicate-webhook-objects
Deduplicate objects in webhook payloads
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
@@ -2,9 +2,11 @@ import {promises as fs} from "fs";
|
|||||||
import Webhook from "./webhook.js";
|
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 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 schema = schemaImport as any;
|
||||||
|
|
||||||
const OUTPUT_PATH = "./src/context-providers/events/webhooks.json";
|
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[];
|
const rawWebhooks = Object.values(schema.webhooks || schema["x-webhooks"]) as any[];
|
||||||
if (!rawWebhooks) {
|
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));
|
await fs.writeFile(OUTPUT_PATH, JSON.stringify(categorizedWebhooks, null, 2));
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {data, DescriptionDictionary} from "@github/actions-expressions";
|
import {data, DescriptionDictionary} from "@github/actions-expressions";
|
||||||
|
|
||||||
import webhooks from "./webhooks.json";
|
import webhooks from "./webhooks.json";
|
||||||
|
import webhookObjects from "./objects.json";
|
||||||
|
|
||||||
import schedule from "./schedule.json" assert {type: "json"};
|
import schedule from "./schedule.json" assert {type: "json"};
|
||||||
import workflow_call from "./workflow_call.json" assert {type: "json"};
|
import workflow_call from "./workflow_call.json" assert {type: "json"};
|
||||||
@@ -47,17 +48,38 @@ type Param = {
|
|||||||
enum?: string[];
|
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 = {
|
type Webhooks = {
|
||||||
[name: string]: {
|
[name: string]: {
|
||||||
[action: string]: {
|
[action: string]: WebhookPayload;
|
||||||
descriptionHtml: string;
|
};
|
||||||
summaryHtml: string;
|
};
|
||||||
bodyParameters: Param[];
|
|
||||||
|
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
|
// Manual work-arounds for webhook issues
|
||||||
@@ -68,7 +90,7 @@ if (inputs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getEventPayload(event: string, action: string = "default"): DescriptionDictionary | undefined {
|
export function getEventPayload(event: string, action: string = "default"): DescriptionDictionary | undefined {
|
||||||
const payload = webhookPayloads?.[event]?.[action];
|
const payload = getWebhookPayload(event, action);
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
// Not all events are real webhooks. Check if there is a custom payload for this event
|
// Not all events are real webhooks. Check if there is a custom payload for this event
|
||||||
const customPayload = customEventPayloads[event];
|
const customPayload = customEventPayloads[event];
|
||||||
@@ -120,3 +142,36 @@ function mergeObject(d: DescriptionDictionary, toAdd: Object): DescriptionDictio
|
|||||||
|
|
||||||
return d;
|
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
Reference in New Issue
Block a user