Skip to content
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

Actions: New PR labeling and issue link workflows #1149

Merged
merged 3 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions .github/workflows/community-first-pr-comment.yml

This file was deleted.

4 changes: 2 additions & 2 deletions .github/workflows/community-issue-comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ jobs:
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: `Thank you @${context.payload.issue.assignee.login} you have been assigned this issue!
**Please follow the directions in our [Contributing Guide](https://github.com/chaynHQ/.github/blob/main/docs/CONTRIBUTING.md). We look forward to reviewing your pull request shortly ✨**
**Please follow the directions in our [Contributing Guide](https://github.com/chaynHQ/.github/blob/main/docs/CONTRIBUTING.md). We look forward to reviewing your pull request. ✨**
---
Support Chayn's mission? ⭐ Please star this repo to help us find more contributors like you!
Learn more about Chayn [here](https://linktr.ee/chayn) and [explore our projects](https://org.chayn.co/projects). 🌸`
Learn more about Chayn and our impact [here](https://github.com/chaynHQ/.github/blob/main/profile/README.md). 🌸`
})
# When issues are labeled as stale, a comment is posted.
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/community-pr-check-issue.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Check Linked Issue on PR
on:
pull_request:
types: [opened, edited]
branches:
- "develop"

jobs:
Check-For-Linked-Issue:
runs-on: ubuntu-latest
if: ${{ github.event.action == 'opened' || github.event.action == 'edited' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Check for keyword and issue number
id: check-for-keyword
uses: actions/github-script@v7
with:
script: |
const script = require('./scripts/github-actions/checkPRLinkedIssue.js')
script({g: github, c: context})
30 changes: 30 additions & 0 deletions .github/workflows/community-pr-labeler.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Auto-label PR from Linked Issue and Review Status

on:
pull_request:
types: [opened, edited, synchronize]
pull_request_review:
types: [submitted, edited, dismissed]

jobs:
auto-label:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Install dependencies
run: npm install @actions/github @actions/core

- name: Run PR labeling script
uses: actions/github-script@v7
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const script = require('./scripts/github-actions/prLabeling.js')
await script({github, context, core})
8 changes: 4 additions & 4 deletions .github/workflows/community-stale-management.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ jobs:
days-before-stale: 30
# disables closing issues
days-before-issue-close: -1
# close pr after 1 week no updates after stale warning
days-before-pr-close: 7
# only scan assigned issues
include-only-assigned: true
Expand All @@ -39,6 +38,7 @@ jobs:
exempt-pr-labels: dependencies
# disable counting irrelevant activity (branch updates) towards day counter on prs.
ignore-pr-updates: true
stale-pr-message: 'As per Chayn policy, after 30 days of inactivity, we will close this PR.'
close-pr-message: 'This PR has been closed due to inactivity.'
stale-issue-message: 'As per Chayn policy, after 30 days of inactivity, we will be unassigning this issue. Please comment to stay assigned.'
# messages skipped, instead handled by issue-comment.yml
stale-pr-message: ''
close-pr-message: ''
stale-issue-message: ''
28 changes: 0 additions & 28 deletions .github/workflows/dependabot-open-issues.yml

This file was deleted.

51 changes: 51 additions & 0 deletions scripts/github-actions/checkPRLinkedIssue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const postIssueComment = require('./utils/postGHComment');

async function main({ g, c }) {
const github = g;
const context = c;

// Retrieve body of context.payload and search for GitHub keywords followed by
// '#' + number. Exclude any matches that are in a comment within the PR body
const prBody = context.payload.pull_request.body;
const prNumber = context.payload.pull_request.number;
const prOwner = context.payload.pull_request.user.login;
const exemptPrOwners = ['kyleecodes', 'swetha-charles', 'eleanorreem', 'annarhughes', 'tarebyte', 'dependabot[bot]', 'dependabot', 'github-actions[bot]', 'github-actions'];
const regex =
/(?!<!--)(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s*#(\d+)(?![^<]*-->)/gi;
const match = prBody.match(regex);

let prComment;

// if no issue linked in description and PR not owned by staff
if (!match && !exemptPrOwners.includes(prOwner)) {
console.log('PR does not have a properly linked issue.');
prComment = `@${prOwner}, this Pull Request is not linked to a valid issue. Above, on the first line of your PR, please link the number of the issue that you worked on using the format of 'Resolves #' + issue number, for example: **_Fixes #9876_**\n\nNote: Do **_not_** use the number of this PR or URL for issue. Chayn staff may disregard this. A linked issue is required for automated PR labeling.`;
} else {
console.log(match[0]);
const [keyword, linkNumber] = match[0].replaceAll('#', '').split(' ');
console.log(`Found a keyword: \'${keyword}\'. Checking for legitimate linked issue...`);

// Check if the linked issue exists in repo
// Issue get request: https://octokit.github.io/rest.js/v20#issues-get
try {
await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: linkNumber,
});
console.log(
`Found an issue: \'#${linkNumber}\' in repo. Reference is a legitimate linked issue.`,
);
} catch (error) {
console.log(`Couldn\'t find issue: \'#${linkNumber}\' in repo. Posting comment...`);
prComment = `@${prOwner}, the issue number referenced above as "**${keyword} #${linkNumber}**" is not found. Please replace with a valid issue number.`;
}
}

// Post comment to PR
if (prComment) {
postIssueComment(prNumber, prComment, github, context);
}
}

module.exports = main;
109 changes: 109 additions & 0 deletions scripts/github-actions/prLabeling.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
module.exports = async ({ github, context, core }) => {
const pr = context.payload.pull_request;
const bodyText = pr.body;
// regex to search for issue linked in description
const issuePattern = /(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s+#(\d+)/i;
const match = bodyText.match(issuePattern);

// reset labels
async function removeAllLabels() {
const currentLabels = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
});

for (const label of currentLabels.data) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
name: label.name,
});
}
}

if (!match) {
console.log('No linked issue found in the pull request description.');
await removeAllLabels();
return;
}

// label PR with linked issue labels
const issueNumber = match[2];
console.log(`Linked issue found: #${issueNumber}`);

try {
const issue = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
});

const issueLabels = issue.data.labels.map((label) => label.name);

// Remove all existing labels
await removeAllLabels();

if (issueLabels.length > 0) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: issueLabels,
});
console.log(`Added labels to PR: ${issueLabels.join(', ')}`);
} else {
console.log('No labels found on the linked issue.');
}

// Update review status label
const reviews = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
});

let reviewStatus = { name: 'needs review', color: 'b60205' };
if (reviews.data.length > 0) {
const latestReview = reviews.data[reviews.data.length - 1];
switch (latestReview.state) {
case 'APPROVED':
reviewStatus = { name: 'review approved', color: '0E8A16' };
break;
case 'CHANGES_REQUESTED':
reviewStatus = { name: 'changes requested', color: 'FBCA04' };
break;
case 'COMMENTED':
reviewStatus = { name: 'review in progress', color: 'FBCA04' };
break;
}
}
await github.rest.issues
.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: reviewStatus.name,
color: reviewStatus.color,
})
.catch((error) => {
if (error.status !== 422) {
// 422 means label already exists
throw error;
}
});

await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: [reviewStatus.name],
});
console.log(
`Updated review status label: ${reviewStatus.name} with color: ${reviewStatus.color}`,
);
} catch (error) {
console.error(`Error processing PR: ${error}`);
core.setFailed(`Error processing PR: ${error.message}`);
}
};
20 changes: 20 additions & 0 deletions scripts/github-actions/utils/postGHComment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Posts a comment on GitHub issues and pull requests
* TODO: reduce repetition and use in other workflow scripts.
* @param {Number} issueNum - the issue number where the comment should be posted
* @param {String} comment - the comment to be posted
*/
async function postComment(issueNum, comment, github, context) {
try {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNum,
body: comment,
});
} catch (err) {
throw new Error(err);
}
}

module.exports = postComment;
Loading