Skip to content

Commit 4562df7

Browse files
authored
ci: github activity slack notifications (#13955)
Slack notifications for popular and new issues
1 parent 3c4f8a3 commit 4562df7

12 files changed

Lines changed: 4809 additions & 0 deletions

File tree

.github/actions/activity/dist/new-issues/index.js

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/actions/activity/dist/popular-issues/index.js

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"private": true,
3+
"license": "MIT",
4+
"scripts": {
5+
"build": "pnpm build:typecheck && pnpm build:popular-issues:ncc && pnpm build:new-issues:ncc",
6+
"build:new-issues:ncc": "ncc build src/new-issues.ts -m -o dist/new-issues",
7+
"build:popular-issues:ncc": "ncc build src/popular-issues.ts -m -o dist/popular-issues",
8+
"build:typecheck": "tsc",
9+
"clean": "rimraf dist"
10+
},
11+
"dependencies": {
12+
"@actions/core": "^1.3.0",
13+
"@actions/github": "^5.0.0",
14+
"@slack/web-api": "^7.10.0"
15+
},
16+
"devDependencies": {
17+
"@octokit/openapi-types": "^26.0.0",
18+
"@octokit/webhooks-types": "^7.5.1",
19+
"@types/node": "^20.16.5",
20+
"@vercel/ncc": "0.38.1",
21+
"typescript": "^4.9.5"
22+
}
23+
}

.github/actions/activity/pnpm-lock.yaml

Lines changed: 4241 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import { run as newIssuesRun } from './new-issues'
2+
import { run as popularIssuesRun } from './popular-issues'
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Format date to <month|short> <day|numeric>, <year|numeric>
3+
*/
4+
export function formattedDate(createdAt: string): string {
5+
const date = new Date(createdAt)
6+
7+
return date.toLocaleDateString('en-US', {
8+
month: 'short',
9+
day: 'numeric',
10+
year: 'numeric',
11+
})
12+
}
13+
14+
/**
15+
* Get days ago in YYYY-MM-DD format
16+
*/
17+
export function daysAgo(days: number): string {
18+
const date = new Date()
19+
date.setDate(date.getDate() - days)
20+
return date.toISOString().split('T')[0]
21+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { info, setFailed } from '@actions/core'
2+
import { getOctokit } from '@actions/github'
3+
import { WebClient } from '@slack/web-api'
4+
import { daysAgo } from './lib/utils'
5+
import { SlimIssue } from './types'
6+
7+
const DAYS_WINDOW = 7
8+
const TRIAGE_LABEL = 'status: needs-triage'
9+
10+
function generateText(issues: SlimIssue[]) {
11+
let text = `*A list of issues opened in the last ${DAYS_WINDOW} with \`status: needs-triage\`:*\n\n`
12+
13+
issues.forEach((issue) => {
14+
text += `• ${issue.title} - (<${issue.html_url}|#${issue.number}>)\n`
15+
})
16+
17+
return text.trim()
18+
}
19+
20+
export async function run() {
21+
try {
22+
if (!process.env.GITHUB_TOKEN) throw new TypeError('GITHUB_TOKEN not set')
23+
if (!process.env.SLACK_TOKEN) throw new TypeError('SLACK_TOKEN not set')
24+
25+
const octoClient = getOctokit(process.env.GITHUB_TOKEN)
26+
const slackClient = new WebClient(process.env.SLACK_TOKEN)
27+
28+
const { data } = await octoClient.rest.search.issuesAndPullRequests({
29+
order: 'desc',
30+
per_page: 15,
31+
q: `repo:payloadcms/payload is:issue is:open label:"${TRIAGE_LABEL}" created:>=${daysAgo(DAYS_WINDOW)}`,
32+
sort: 'created',
33+
})
34+
35+
if (!data.items.length) {
36+
info(`No popular issues`)
37+
return
38+
}
39+
40+
const messageText = generateText(data.items)
41+
console.log(messageText)
42+
43+
await slackClient.chat.postMessage({
44+
text: generateText(data.items),
45+
channel: process.env.DEBUG === 'true' ? '#test-slack-notifications' : '#dev-feed',
46+
icon_emoji: ':github:',
47+
username: 'GitHub Notifier',
48+
})
49+
info(`Posted to Slack!`)
50+
} catch (error) {
51+
setFailed(error as Error)
52+
}
53+
}
54+
55+
run()
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { info, setFailed } from '@actions/core'
2+
import { getOctokit } from '@actions/github'
3+
import { WebClient } from '@slack/web-api'
4+
import { daysAgo } from './lib/utils'
5+
import { SlimIssue } from './types'
6+
7+
const DAYS_WINDOW = 90
8+
9+
function generateText(issues: SlimIssue[]) {
10+
let text = `*A list of the top 10 issues sorted by the most reactions over the last ${DAYS_WINDOW} days:*\n\n`
11+
12+
// Format date as "X days ago"
13+
const formattedDaysAgo = (dateString: string) => {
14+
const date = new Date(dateString)
15+
const now = new Date()
16+
const diffTime = Math.abs(now.getTime() - date.getTime())
17+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
18+
return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`
19+
}
20+
21+
issues.forEach((issue) => {
22+
text += `• ${issue?.reactions?.total_count || 0} 👍 ${issue.title} - <${issue.html_url}|#${issue.number}>, ${formattedDaysAgo(issue.created_at)}\n`
23+
})
24+
25+
return text.trim()
26+
}
27+
28+
export async function run() {
29+
try {
30+
if (!process.env.GITHUB_TOKEN) throw new TypeError('GITHUB_TOKEN not set')
31+
if (!process.env.SLACK_TOKEN) throw new TypeError('SLACK_TOKEN not set')
32+
33+
const octoClient = getOctokit(process.env.GITHUB_TOKEN)
34+
const slackClient = new WebClient(process.env.SLACK_TOKEN)
35+
36+
const { data } = await octoClient.rest.search.issuesAndPullRequests({
37+
order: 'desc',
38+
per_page: 10,
39+
q: `repo:payloadcms/payload is:issue is:open created:>=${daysAgo(DAYS_WINDOW)}`,
40+
sort: 'reactions',
41+
})
42+
43+
if (!data.items.length) {
44+
info(`No popular issues`)
45+
return
46+
}
47+
48+
const messageText = generateText(data.items)
49+
console.log(messageText)
50+
51+
await slackClient.chat.postMessage({
52+
text: messageText,
53+
channel: process.env.DEBUG === 'true' ? '#test-slack-notifications' : '#dev-feed',
54+
icon_emoji: ':github:',
55+
username: 'GitHub Notifier',
56+
})
57+
58+
info(`Posted to Slack!`)
59+
} catch (error) {
60+
setFailed(error as Error)
61+
}
62+
}
63+
64+
run()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { components } from '@octokit/openapi-types'
2+
3+
export type SlimIssue = Pick<
4+
components['schemas']['issue-search-result-item'],
5+
'html_url' | 'number'
6+
> & {
7+
title: string
8+
created_at: string
9+
reactions?: components['schemas']['reaction-rollup']
10+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"compilerOptions": {
3+
"target": "esnext",
4+
"noEmit": true,
5+
"strict": true,
6+
"noUnusedLocals": false, // Undo this
7+
"noImplicitReturns": true,
8+
"noFallthroughCasesInSwitch": true,
9+
"forceConsistentCasingInFileNames": true,
10+
"downlevelIteration": true,
11+
"skipLibCheck": true,
12+
"moduleResolution": "node",
13+
},
14+
"exclude": [
15+
"src/**/*.test.ts"
16+
]
17+
}

0 commit comments

Comments
 (0)