Now why would I want to do this huh? 🤔
Well, we have a private repo where maintainers are able to make requests to join the private maintainer community.
Someone thought it'd be cute to submit 1000+ issues to the repo with the handle @undefined
and the title undefined
. This was a problem because it was making it difficult to find the actual issues that were being submitted by maintainers that needed to be approved.
We need to do some additional work to the repo before I'm able to add validation to the issue form but in the meantime, we needed to close these issues. So, I thought a temporary solution would be to create a GitHub action that closed all of the issues - with the title containing Pending invitation request for: @undefined'
. And it worked!!
We went from having over 1,600+ issues to 64 valid issues in a matter of minutes.
Here's how I did it.
Creating the GitHub Action
The first thing I did was go to my bestie chatty (GitHub Copilot Chat) and asked it if I could close 1000+ issues with a GitHub action. It gave me a few options, but I decided to go with this one:
Then it generated the closeIssue.js
file which looked like this:
Sweet, that was the bulk of the work. Now I just needed to add the action to my repo.
I created a file called close_issues.yml
in the .github/workflows
directory and added the following code:
name: Close Undefined Issues
on:
workflow_dispatch:
push:
branches: [ "main" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
schedule:
- cron: '43 0 * * 1'
jobs:
close_issues:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 14
- name: Install dependencies
run: npm install
- name: Run script
run: node closeIssues.js
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
I made a few tweaks to the code, but all in all, this code was written largely in collaboration with GitHub Copilot.
The next file I created was the closeIssues.js
file. I added the following code:
import { Octokit } from "@octokit/rest";
const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN,
});
async function closeIssues() {
const { data: issues } = await octokit.issues.listForRepo({
owner: 'OWNER',
repo: 'internal-repo',
});
const issuesToClose = issues.filter(issue => issue.title === 'Pending invitation request for: @undefined');
for (const issue of issuesToClose) {
await octokit.issues.update({
owner: 'OWNER',
repo: 'internal-repo',
issue_number: issue.number,
state: 'closed',
});
}
}
closeIssues().catch(console.error);
I added a package.json
file to my repo, install the needed dependency, and then pushed the code to my repo.
Testing the Action
After about 3 (ok 4!) tries, I finally got the action to run successfully.
Initially, the action closed 30 issues at a time, because that is the limit for the GitHub API. So, I had to read the docs 🤯 to understand how to grab ALL issues that matched the criteria and closed them.
Updates
After reading the docs, I realized that I needed to use the paginate()
method to get all of the issues for the specified repo. I also learned that with paginate, the issues are returned as an array, so I no longer needed const { data: issues }
. I also needed to use octokit.rest.issues
when making requests.
In the end, the code that ran successfully and closed all 1000+ issues AT ONCE looked like this:
import { Octokit } from "@octokit/rest";
const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN,
});
async function closeIssues() {
try {
console.log('Fetching issues...');
const issues = await octokit.paginate(octokit.rest.issues.listForRepo, {
owner: 'OWNER',
repo: 'internal-repo',
state: 'open',
});
console.log(`Fetched ${issues.length} issues.`);
const issuesToClose = issues.filter(issue => issue.title === 'Pending invitation request for: @undefined');
console.log(`Found ${issuesToClose.length} issues to close.`);
for (const issue of issuesToClose) {
console.log(`Closing issue #${issue.number}...`);
await octokit.rest.issues.update({
owner: 'OWNER',
repo: 'internal-repo',
issue_number: issue.number,
state: 'closed',
});
console.log(`Closed issue #${issue.number}.`);
}
} catch (error) {
console.error(error);
}
}
closeIssues();
I added some logging to the code so I could see what was happening during the workflow run. I also added a try/catch block to catch any errors that may occur.
Conclusion
I was very happy that I could do this because it would've taken my teammate HOURS to close all these issues manually. I was able to do it in a matter of minutes! I'm super stoked that GitHub Copilot was able to teach me something new - how to interact with the GitHub API using the Octokit library. 🚀
Have you ever used GitHub Copilot to help you write GitHub Actions? Let me know in the comments below! I hope you learned something new today.
Until next time, happy coding! 😁