From 0c75306ea3abd858b79fb6ec4e4dfe78cc55c168 Mon Sep 17 00:00:00 2001 From: Pablo Pettinari Date: Tue, 19 Mar 2024 16:26:12 +0100 Subject: [PATCH 1/5] create new api route to handle gfi webhook --- src/lib/utils/gh.ts | 32 ++++++++++++ src/pages/api/gfi-issues-webhook.ts | 75 +++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/pages/api/gfi-issues-webhook.ts diff --git a/src/lib/utils/gh.ts b/src/lib/utils/gh.ts index 617d74b024e..4db62d4e1f3 100644 --- a/src/lib/utils/gh.ts +++ b/src/lib/utils/gh.ts @@ -44,3 +44,35 @@ export const getLastModifiedDateByPath = (path: string): string => { const logInfo = getGitLogFromPath(path) return extractDateFromGitLogInfo(logInfo) } + +const LABELS_TO_SEARCH = ["content", "design", "dev", "doc", "translation"] +const LABELS_TO_TEXT = { + content: "content", + design: "design", + dev: "dev", + doc: "docs", + translation: "translation", +} + +// Given a list of labels, it returns a string with the labels that match the +// LABELS_TO_SEARCH list, using the LABELS_TO_TEXT values +// Example: +// - ["content :pencil:", "ux design"] => "content, design" +// - ["documentation :emoji:", "dev required", "good first issue"] => "docs, dev" +export const rawLabelsToText = (labels: string[]) => { + return labels + .map((label) => { + const labelIndex = LABELS_TO_SEARCH.findIndex((l) => + label.toLocaleLowerCase().includes(l) + ) + + if (labelIndex === -1) { + return + } + + const labelMatched = LABELS_TO_SEARCH[labelIndex] + return LABELS_TO_TEXT[labelMatched] + }) + .filter(Boolean) + .join(", ") +} diff --git a/src/pages/api/gfi-issues-webhook.ts b/src/pages/api/gfi-issues-webhook.ts new file mode 100644 index 00000000000..05aa23d2a50 --- /dev/null +++ b/src/pages/api/gfi-issues-webhook.ts @@ -0,0 +1,75 @@ +import type { NextApiRequest, NextApiResponse } from "next" + +import { rawLabelsToText } from "@/lib/utils/gh" + +type ResponseData = { + message: string +} + +const GFI_LABEL = "good first issue" + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const { method } = req + + if (method !== "POST") { + return res.status(405).json({ message: "Method not allowed" }) + } + + const { action, label, issue } = req.body + + if (action !== "labeled") { + return res.status(200).json({ message: "Not a label action" }) + } + + if (label.name !== GFI_LABEL) { + return res.status(200).json({ message: "Not a good first issue" }) + } + + if (issue.assignee) { + return res.status(200).json({ message: "Issue already assigned" }) + } + + // send a notification to discord webhook + const webhookUrl = `https://discord.com/api/webhooks/${process.env.DISCORD_ID}/${process.env.DISCORD_TOKEN}` + + const embeds = [ + { + title: issue.title, + url: issue.html_url, + timestamp: issue.created_at, + description: issue.labels.map((label) => label.name).join(" • "), + color: 10181046, // purple + author: { + name: issue.user.login, + url: issue.user.html_url, + icon_url: issue.user.avatar_url, + }, + }, + ] + + const allLabels = issue.labels.map((label) => label.name) + const labels = rawLabelsToText(allLabels) + const labelsText = labels ? ` - ${labels}` : "" + + const message = { + content: `## New good first issue${labelsText}`, + embeds, + } + + const discordRes = await fetch(webhookUrl, { + method: "post", + body: JSON.stringify(message), + headers: { "Content-Type": "application/json" }, + }) + + if (!discordRes.ok) { + const error = await discordRes.json() + console.log(error) + return res.status(500).json({ message: "Error sending GFI to Discord" }) + } + + res.status(200).json({ message: "New GFI sent to Discord!" }) +} From 641deaa6a9fdd7115bf2ef1bb764cadacfe01b9c Mon Sep 17 00:00:00 2001 From: Pablo Pettinari Date: Thu, 21 Mar 2024 14:49:12 +0100 Subject: [PATCH 2/5] update message copy --- src/lib/utils/gh.ts | 14 ++++++++------ src/pages/api/gfi-issues-webhook.ts | 18 ++++++++++++++---- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/lib/utils/gh.ts b/src/lib/utils/gh.ts index 4db62d4e1f3..61a2405c9ce 100644 --- a/src/lib/utils/gh.ts +++ b/src/lib/utils/gh.ts @@ -54,13 +54,13 @@ const LABELS_TO_TEXT = { translation: "translation", } -// Given a list of labels, it returns a string with the labels that match the +// Given a list of labels, it returns a new array with the labels that match the // LABELS_TO_SEARCH list, using the LABELS_TO_TEXT values // Example: -// - ["content :pencil:", "ux design"] => "content, design" -// - ["documentation :emoji:", "dev required", "good first issue"] => "docs, dev" -export const rawLabelsToText = (labels: string[]) => { - return labels +// - ["content :pencil:", "ux design"] => ["content", "design"] +// - ["documentation :emoji:", "dev required", "good first issue"] => ["docs", "dev"] +export const normalizeLabels = (labels: string[]) => { + const labelsFound = labels .map((label) => { const labelIndex = LABELS_TO_SEARCH.findIndex((l) => label.toLocaleLowerCase().includes(l) @@ -74,5 +74,7 @@ export const rawLabelsToText = (labels: string[]) => { return LABELS_TO_TEXT[labelMatched] }) .filter(Boolean) - .join(", ") + + // remove duplicates + return Array.from(new Set(labelsFound)) } diff --git a/src/pages/api/gfi-issues-webhook.ts b/src/pages/api/gfi-issues-webhook.ts index 05aa23d2a50..03a5152ec20 100644 --- a/src/pages/api/gfi-issues-webhook.ts +++ b/src/pages/api/gfi-issues-webhook.ts @@ -1,6 +1,14 @@ import type { NextApiRequest, NextApiResponse } from "next" -import { rawLabelsToText } from "@/lib/utils/gh" +import { normalizeLabels } from "@/lib/utils/gh" + +const LABELS_TO_EMOJI = { + content: "📝", + design: "🎨", + dev: "🛠️", + docs: "📚", + translation: "🌐", +} type ResponseData = { message: string @@ -51,11 +59,13 @@ export default async function handler( ] const allLabels = issue.labels.map((label) => label.name) - const labels = rawLabelsToText(allLabels) - const labelsText = labels ? ` - ${labels}` : "" + const [firstLabel] = normalizeLabels(allLabels) + const labelsText = firstLabel ? ` - ${firstLabel}` : "" + const emoji = LABELS_TO_EMOJI[firstLabel] + const emojiText = emoji ? `${emoji} ` : "" const message = { - content: `## New good first issue${labelsText}`, + content: `### ${emojiText}New good first issue${labelsText}`, embeds, } From e953e6014581a6d47dd99f11e6e88133a989b920 Mon Sep 17 00:00:00 2001 From: Pablo Pettinari Date: Thu, 21 Mar 2024 16:16:19 +0100 Subject: [PATCH 3/5] add event type label --- src/lib/utils/gh.ts | 10 +++++++++- src/pages/api/gfi-issues-webhook.ts | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lib/utils/gh.ts b/src/lib/utils/gh.ts index 61a2405c9ce..a31581364fd 100644 --- a/src/lib/utils/gh.ts +++ b/src/lib/utils/gh.ts @@ -45,13 +45,21 @@ export const getLastModifiedDateByPath = (path: string): string => { return extractDateFromGitLogInfo(logInfo) } -const LABELS_TO_SEARCH = ["content", "design", "dev", "doc", "translation"] +const LABELS_TO_SEARCH = [ + "content", + "design", + "dev", + "doc", + "translation", + "event", +] const LABELS_TO_TEXT = { content: "content", design: "design", dev: "dev", doc: "docs", translation: "translation", + event: "event", } // Given a list of labels, it returns a new array with the labels that match the diff --git a/src/pages/api/gfi-issues-webhook.ts b/src/pages/api/gfi-issues-webhook.ts index 03a5152ec20..6cfc97cbce2 100644 --- a/src/pages/api/gfi-issues-webhook.ts +++ b/src/pages/api/gfi-issues-webhook.ts @@ -8,6 +8,7 @@ const LABELS_TO_EMOJI = { dev: "🛠️", docs: "📚", translation: "🌐", + event: "🗓️", } type ResponseData = { From f9487c3b48e0b765bf29f6de327e51001a4a8c7a Mon Sep 17 00:00:00 2001 From: Pablo Pettinari Date: Fri, 22 Mar 2024 15:59:43 +0100 Subject: [PATCH 4/5] improve type safety in constants --- src/lib/utils/gh.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/utils/gh.ts b/src/lib/utils/gh.ts index a31581364fd..dca55704d8d 100644 --- a/src/lib/utils/gh.ts +++ b/src/lib/utils/gh.ts @@ -52,8 +52,9 @@ const LABELS_TO_SEARCH = [ "doc", "translation", "event", -] -const LABELS_TO_TEXT = { +] as const + +const LABELS_TO_TEXT: Record<(typeof LABELS_TO_SEARCH)[number], string> = { content: "content", design: "design", dev: "dev", From d09ba0da3f6546dbae0f59fb21d278eda073d409 Mon Sep 17 00:00:00 2001 From: Pablo Pettinari Date: Fri, 22 Mar 2024 16:00:19 +0100 Subject: [PATCH 5/5] refactor, simplify content message logic --- src/pages/api/gfi-issues-webhook.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/pages/api/gfi-issues-webhook.ts b/src/pages/api/gfi-issues-webhook.ts index 6cfc97cbce2..da398756abd 100644 --- a/src/pages/api/gfi-issues-webhook.ts +++ b/src/pages/api/gfi-issues-webhook.ts @@ -61,12 +61,19 @@ export default async function handler( const allLabels = issue.labels.map((label) => label.name) const [firstLabel] = normalizeLabels(allLabels) - const labelsText = firstLabel ? ` - ${firstLabel}` : "" - const emoji = LABELS_TO_EMOJI[firstLabel] - const emojiText = emoji ? `${emoji} ` : "" + + let content: string + if (firstLabel) { + const labelsText = ` - ${firstLabel}` + const emoji = LABELS_TO_EMOJI[firstLabel] + const emojiText = emoji ? `${emoji} ` : "" + content = `### ${emojiText}New good first issue${labelsText}` + } else { + content = `### New good first issue` + } const message = { - content: `### ${emojiText}New good first issue${labelsText}`, + content, embeds, }