-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use GH Actions to post EOD activities to slack (#441)
- Loading branch information
1 parent
31d8a55
commit 2bc4806
Showing
10 changed files
with
383 additions
and
373 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
const { | ||
getContributors, | ||
getEvents, | ||
getEODUpdates, | ||
postEODMessage, | ||
mergeUpdates, | ||
flushEODUpdates, | ||
} = require("./utils"); | ||
|
||
async function main() { | ||
const allContributors = await getContributors(); | ||
console.info(`⚙️ Found ${Object.keys(allContributors).length} contributors`); | ||
|
||
console.info("⚙️ Fetching events from GitHub"); | ||
const allEvents = await getEvents(Object.keys(allContributors)); | ||
|
||
console.info("⚙️ Fetching General EOD updates"); | ||
const allEodUpdates = await getEODUpdates(); | ||
|
||
console.info("⚙️ Ready to post EOD updates onto Slack Channel"); | ||
for (const [github, slack] of Object.entries(allContributors)) { | ||
if (github !== "rithviknishad") continue; // TODO: remove this before pushing to prod | ||
|
||
const events = allEvents[github] ?? []; | ||
const eodUpdates = allEodUpdates[github] ?? []; | ||
|
||
const activityCount = events.length + eodUpdates.length; | ||
if (activityCount === 0) { | ||
console.info(`- ⏭️ ${github}: Skipping due to no activity.`); | ||
continue; | ||
} | ||
|
||
await postEODMessage({ | ||
github, | ||
slack, | ||
updates: mergeUpdates(events, eodUpdates), | ||
}); | ||
console.info(`- ✅ ${github}: Posted ${activityCount} updates.`); | ||
} | ||
|
||
// console.info("Flushing EOD updates from cache."); | ||
// await flushEODUpdates(); | ||
|
||
console.info("✅ Completed!"); | ||
} | ||
|
||
main(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,259 @@ | ||
const { readFile, readdir } = require("fs/promises"); | ||
const { join } = require("path"); | ||
const matter = require("gray-matter"); | ||
|
||
const { Octokit } = require("@octokit/action"); | ||
// const { Octokit } = require("octokit"); | ||
|
||
const root = join(process.cwd(), "data-repo/contributors"); | ||
|
||
async function getContributorBySlug(file) { | ||
const { data } = matter(await readFile(join(root, file), "utf8")); | ||
return { | ||
github: file.replace(/\.md$/, ""), | ||
slack: data.slack, | ||
}; | ||
} | ||
|
||
export async function getContributors() { | ||
const slugs = await readdir(`${root}`); | ||
const contributors = await Promise.all( | ||
slugs.map((path) => getContributorBySlug(path)), | ||
); | ||
|
||
return Object.fromEntries( | ||
contributors | ||
.filter((contributor) => !!contributor.slack) | ||
.map((c) => [c.github, c.slack]), | ||
); | ||
} | ||
|
||
export const GITHUB_ORG = process.env.GITHUB_ORG; | ||
export const GITHUB_TOKEN = process.env.GITHUB_TOKEN; | ||
export const LEADERBOARD_API_KEY = process.env.LEADERBOARD_API_KEY; | ||
export const LEADERBOARD_URL = process.env.LEADERBOARD_URL; | ||
export const SLACK_EOD_BOT_TOKEN = process.env.SLACK_EOD_BOT_TOKEN; | ||
export const SLACK_EOD_BOT_CHANNEL = process.env.SLACK_EOD_BOT_CHANNEL; | ||
|
||
function isAllowedEvent(event) { | ||
if (event.type === "PullRequestEvent") { | ||
return event.payload.action === "opened"; | ||
} | ||
|
||
if (event.type === "PullRequestReviewEvent") { | ||
return true; | ||
} | ||
} | ||
|
||
export const octokit = new Octokit({ auth: GITHUB_TOKEN }); | ||
|
||
export async function getEvents(allowedAuthors) { | ||
const aDayAgoDate = new Date(); | ||
aDayAgoDate.setDate(aDayAgoDate.getDate() - 1); | ||
const aDayAgo = aDayAgoDate.getTime(); | ||
|
||
const events = await octokit.paginate( | ||
"GET /orgs/{org}/events", | ||
{ org: GITHUB_ORG, per_page: 1000 }, | ||
(res) => | ||
res.data.filter( | ||
(event) => | ||
allowedAuthors.includes(event.actor.login) && | ||
isAllowedEvent(event) && | ||
event.created_at && | ||
new Date(event.created_at).getTime() > aDayAgo, | ||
), | ||
); | ||
|
||
return Object.groupBy(events, (e) => e.actor.login); | ||
} | ||
|
||
export function mergeUpdates(events, eodUpdates) { | ||
const updates = []; | ||
const counts = { | ||
eod_updates: eodUpdates.length, | ||
pull_requests: 0, | ||
reviews: 0, | ||
}; | ||
|
||
updates.push(...eodUpdates.map((title) => ({ title }))); | ||
|
||
for (const event of events) { | ||
if (event.type === "PullRequestReviewEvent") { | ||
const url = event.payload.pull_request.html_url; | ||
if (!updates.find((a) => a.url === url)) { | ||
counts.reviews += 1; | ||
updates.push({ | ||
title: `Reviewed PR: "${event.payload.pull_request.title}"`, | ||
url, | ||
}); | ||
} | ||
} | ||
|
||
if ( | ||
event.type === "PullRequestEvent" && | ||
event.payload.action === "opened" | ||
) { | ||
counts.pull_requests += 1; | ||
updates.push({ | ||
title: `Opened PR: "${event.payload.pull_request.title}"`, | ||
url: event.payload.pull_request.html_url, | ||
}); | ||
} | ||
} | ||
|
||
return { updates, counts }; | ||
} | ||
|
||
const leaderboardApiHeaders = { | ||
"Content-Type": "application/json", | ||
Authorization: `${LEADERBOARD_API_KEY}`, | ||
}; | ||
|
||
export async function getEODUpdates() { | ||
const res = await fetch(LEADERBOARD_URL, { | ||
headers: leaderboardApiHeaders, | ||
}); | ||
return res.json(); | ||
} | ||
|
||
export async function flushEODUpdates() { | ||
const res = await fetch(LEADERBOARD_URL, { | ||
headers: leaderboardApiHeaders, | ||
method: "DELETE", | ||
}); | ||
return res.json(); | ||
} | ||
|
||
const slackApiHeaders = { | ||
"Content-Type": "application/json", | ||
Authorization: `Bearer ${SLACK_EOD_BOT_TOKEN}`, | ||
}; | ||
|
||
async function sendSlackMessage(channel, text, blocks) { | ||
const res = await fetch("https://slack.com/api/chat.postMessage", { | ||
method: "POST", | ||
headers: slackApiHeaders, | ||
body: JSON.stringify({ | ||
// channel, | ||
channel: "U02TDGQQPMJ", // TODO: replace with channel before pushign | ||
text, | ||
...blocks, | ||
}), | ||
}); | ||
|
||
const data = await res.json(); | ||
if (!data.ok) { | ||
console.error(data); | ||
} | ||
} | ||
|
||
export function getHumanReadableUpdates( | ||
{ updates, counts }, | ||
slackID, | ||
githubId, | ||
) { | ||
const colorRange = [ | ||
{ color: "#00FF00", min: 5 }, | ||
{ color: "#FFFF00", min: 1 }, | ||
{ color: "#FF0000", min: 0 }, | ||
]; | ||
|
||
const activityCount = | ||
counts.pull_requests + counts.reviews + counts.eod_updates; | ||
|
||
const color = | ||
colorRange.find((range) => range.min <= activityCount)?.color || "#0000FF"; | ||
|
||
return { | ||
attachments: [ | ||
{ | ||
color, | ||
blocks: [ | ||
{ | ||
type: "section", | ||
text: { | ||
type: "mrkdwn", | ||
text: ` | ||
*EOD Updates for <@${slackID}>* | ||
Summary: Opened *${counts.pull_requests}* pull requests, Reviewed *${counts.reviews}* PRs and *${counts.eod_updates}* other general updates. | ||
<https://github.com/${githubId}|GitHub Profile> | <${process.env.NEXT_PUBLIC_META_URL}/contributors/${githubId}|Contributor Profile> | ||
`, | ||
}, | ||
accessory: { | ||
type: "image", | ||
image_url: `https://avatars.githubusercontent.com/${githubId}?s=128`, | ||
alt_text: "profile image", | ||
}, | ||
}, | ||
|
||
{ | ||
type: "divider", | ||
}, | ||
{ | ||
type: "rich_text", | ||
elements: [ | ||
{ | ||
type: "rich_text_section", | ||
elements: [ | ||
{ | ||
type: "text", | ||
text: "Updates:", | ||
style: { bold: true }, | ||
}, | ||
{ | ||
type: "text", | ||
text: `\n${updates.length === 0 ? "No updates for today" : ""}`, | ||
}, | ||
], | ||
}, | ||
{ | ||
type: "rich_text_list", | ||
style: "bullet", | ||
elements: updates.map((item) => { | ||
const elements = [ | ||
{ | ||
type: "text", | ||
text: item.title, | ||
}, | ||
]; | ||
|
||
if (item.url) { | ||
let preview = ""; | ||
|
||
if (item.url.startsWith("https://github.com")) { | ||
preview = item.url.replace("https://github.com/", ""); | ||
const [org, repo, type, number] = preview.split("/"); | ||
preview = `${repo}#${number.split("#")[0]}`; | ||
} | ||
|
||
elements.push({ | ||
type: "link", | ||
text: ` ${preview} ↗`, | ||
url: item.url, | ||
}); | ||
} | ||
|
||
return { | ||
type: "rich_text_section", | ||
elements, | ||
}; | ||
}), | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
], | ||
}; | ||
} | ||
|
||
export async function postEODMessage({ github, slack, updates }) { | ||
await sendSlackMessage( | ||
SLACK_EOD_BOT_CHANNEL, | ||
"", | ||
getHumanReadableUpdates(updates, slack, github), | ||
); | ||
} |
3 changes: 1 addition & 2 deletions
3
app/api/slack-eod-bot/cron/post-update/[username]/preview/route.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.