-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create congratulations bot #5404
Changes from 4 commits
fe43591
ef6e1cc
cfa260d
9d51cd9
adef3da
7ca48d5
519166c
aca6281
f97d3c2
375518b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
name: Congratulate Contributors | ||
|
||
on: | ||
# it's usually not recommended to use pull_request_target | ||
# but we consider it's safe here if we keep the same steps | ||
# see: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ | ||
# and: https://github.com/facebook/react-native/pull/34370/files | ||
pull_request_target: | ||
types: [closed] | ||
permissions: | ||
pull-requests: write | ||
jobs: | ||
congratulate: | ||
if: github.event.pull_request.merged == true | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- name: Install dependencies | ||
uses: ./.github/workflows/actions/yarn-install | ||
- name: Run congratulate-dangerfile.js | ||
run: cd packages/twenty-utils && npx nx danger:congratulate | ||
env: | ||
DANGER_GITHUB_API_TOKEN: ${{ github.token }} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { danger } from 'danger'; | ||
|
||
const ordinalSuffix = (number) => { | ||
const v = number % 100; | ||
if (v === 11 || v === 12 || v === 13) { | ||
return number + 'th'; | ||
} | ||
const suffixes = { 1: 'st', 2: 'nd', 3: 'rd' }; | ||
return number + (suffixes[v % 10] || 'th'); | ||
}; | ||
|
||
const fetchContributorStats = async (username: string) => { | ||
const apiUrl = `https://twenty.com/api/contributors/contributorStats/${username}`; | ||
|
||
const response = await fetch(apiUrl); | ||
const data = await response.json(); | ||
return data; | ||
}; | ||
|
||
const fetchContributorImage = async (username: string) => { | ||
const apiUrl = `https://twenty.com/api/contributors/${username}/og.png`; | ||
|
||
await fetch(apiUrl); | ||
}; | ||
|
||
const runCongratulate = async () => { | ||
const pullRequest = danger.github.pr; | ||
const userName = pullRequest.user.login; | ||
|
||
const { data: pullRequests } = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this going to be posted on every PR? We want to discard the |
||
await danger.github.api.rest.search.issuesAndPullRequests({ | ||
q: `is:pr author:${userName} is:closed repo:twentyhq/twenty`, | ||
per_page: 2, | ||
page: 1, | ||
}); | ||
|
||
let stats; | ||
const isFirstPR = pullRequests.total_count === 1; | ||
|
||
if (!isFirstPR) { | ||
stats = await fetchContributorStats(userName); | ||
} else { | ||
stats = { mergedPRsCount: 1, rank: 52 }; | ||
} | ||
|
||
const contributorUrl = `https://twenty.com/contributors/${userName}`; | ||
|
||
// Pre-fetch to trigger cloudflare cache | ||
await fetchContributorImage(userName); | ||
|
||
const message = | ||
`Thanks @${userName} for your contribution!\n` + | ||
`This marks your **${ordinalSuffix( | ||
stats.mergedPRsCount, | ||
)}** PR on the repo. ` + | ||
`You're **top ${stats.rank}%** of all our contributors 🎉\n` + | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe hide this is if it's their first contribution at least? Feels a bit strange otherwise There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed ! |
||
`[See contributor page](${contributorUrl}) - ` + | ||
`[Share on LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=${contributorUrl}) - ` + | ||
`[Share on Twitter](https://www.twitter.com/share?url=${contributorUrl})\n\n` + | ||
`![Contributions](https://twenty.com/api/contributors/${userName}/og.png)`; | ||
|
||
await danger.github.api.rest.issues.createComment({ | ||
owner: danger.github.thisPR.owner, | ||
repo: danger.github.thisPR.repo, | ||
issue_number: danger.github.thisPR.pull_number, | ||
body: message, | ||
}); | ||
}; | ||
|
||
if (danger.github && danger.github.pr.merged) { | ||
runCongratulate(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { getContributorActivity } from '@/app/contributors/utils/get-contributor-activity'; | ||
|
||
export const dynamic = 'force-dynamic'; | ||
|
||
export async function GET(request: Request) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The issue is that this isn't going to be accurate at the moment it's called with our cron? or will it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, I had already thought this through. In my opinion it wouldn’t be an issue because the interval between the creation of a PR, its review, and subsequent merging is likely to be longer than the 5-minute interval of the scheduled cron job. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would if we were showing "Total PRs" but we show "Merged PRs" on the card. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, it's problematic ! Okay I can change it to "TotalPRs" for now until a better solution is found There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The rank would also have to be changed as it is based on mergedPRs :( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. arf then maybe expose an endpoint which you call before pre-fetching the image to launch a re-import for that user? and we keep range/total merged PRs There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see a better solution (or plug Github webhooks) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any chance we can do a github:sync ? How often do we merge PRs from contributors (for rate limits)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes sharing the code from github:sync works, then we'll re-expose it as an endpoint! We can keep both There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay perfect ! |
||
try { | ||
const url = request.url; | ||
|
||
const username = url.split('/')?.pop() || ''; | ||
|
||
const contributorActivity = await getContributorActivity(username); | ||
|
||
if (contributorActivity) { | ||
const mergedPRsCount = contributorActivity.mergedPRsCount; | ||
const rank = contributorActivity.rank; | ||
return Response.json({ mergedPRsCount, rank }); | ||
} | ||
} catch (error: any) { | ||
return new Response(`Contributor stats error: ${error?.message}`, { | ||
status: 500, | ||
}); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we add this as a step in utils instead of a dedicated workflow? It will show up as a new step in CI otherwise which would be a bit strange
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran multiple tests and unfortunately we can’t add the congratulations bot as a new step in CI. Basically as of right now, CI is being triggered for the following types of activity:
The bot needs to be triggered on the closed event.
We don’t want to add closed for the existing danger file as it wouldn’t make any sense + we don’t want the bot triggered on the 3 other events.
So what we could do to avoid having another workflow is add another job to CI (danger + congratulate). Making sure danger is triggered for 3 events and congratulate for closed only. Basically the job would be skipped for event types that they don’t correspond to. This would allow us to have one workflow but with 2 different jobs. Let me know if you would rather that or have two separate workflows?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I think that would be best! Thanks