Skip to content

Commit c5d42ba

Browse files
committed
[feature/releaser] Automate assigning/unassigning issues #571
I've created four GitHub Actions workflow files to automate issue assignment/unassignment. Created Workflows: 1. issue-assignment.yml – Auto-response to 'assign me' requests 2. pr-issue-link.yml – Auto-assign on PR 3. stale-pr-management.yml – Stale PR management (tracks contributor inactivity only) 4. pr-reopen-reassign.yml – Handle contributor follow-up Fixes #541
1 parent f3fc7c2 commit c5d42ba

File tree

4 files changed

+510
-0
lines changed

4 files changed

+510
-0
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: Issue Assignment Bot
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
7+
permissions:
8+
issues: write
9+
10+
jobs:
11+
respond-to-assign-request:
12+
runs-on: ubuntu-latest
13+
if: |
14+
github.event.issue.pull_request == null &&
15+
(
16+
contains(github.event.comment.body, 'assign this issue to me') ||
17+
contains(github.event.comment.body, 'assign me') ||
18+
contains(github.event.comment.body, 'can I work on this') ||
19+
contains(github.event.comment.body, 'I would like to work on this') ||
20+
contains(github.event.comment.body, 'I want to work on this')
21+
)
22+
steps:
23+
- name: Respond to assignment request
24+
uses: actions/github-script@v7
25+
with:
26+
github-token: ${{ secrets.GITHUB_TOKEN }}
27+
script: |
28+
const contributingUrl = 'http://openwisp.io/docs/developer/contributing.html';
29+
const commenter = context.payload.comment.user.login;
30+
31+
const message = `Hi @${commenter} 👋,
32+
33+
Thank you for your interest in contributing to OpenWISP! 🎉
34+
35+
According to our [contributing guidelines](${contributingUrl}), **you don't need to wait to be assigned** to start working on an issue. We encourage you to:
36+
37+
1. **Fork the repository** and start working on your solution
38+
2. **Open a Pull Request (PR) as soon as possible** - even as a draft if it's still in progress
39+
3. **Link your PR to this issue** by including \`Fixes #${context.payload.issue.number}\` in the PR description
40+
41+
Once you open a PR that references this issue, you will be automatically assigned to it.
42+
43+
This approach helps us:
44+
- See your progress and provide early feedback
45+
- Avoid multiple contributors working on the same issue unknowingly
46+
- Keep the contribution process moving smoothly
47+
48+
We look forward to your contribution! If you have any questions, feel free to ask in the PR or check our [documentation](${contributingUrl}).
49+
50+
Happy coding! 🚀`;
51+
52+
await github.rest.issues.createComment({
53+
owner: context.repo.owner,
54+
repo: context.repo.repo,
55+
issue_number: context.payload.issue.number,
56+
body: message
57+
});
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: PR Issue Auto-Assignment
2+
3+
on:
4+
pull_request_target:
5+
types: [opened, reopened]
6+
7+
permissions:
8+
issues: write
9+
pull-requests: read
10+
11+
jobs:
12+
auto-assign-issue:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Auto-assign linked issues to PR author
16+
uses: actions/github-script@v7
17+
with:
18+
github-token: ${{ secrets.GITHUB_TOKEN }}
19+
script: |
20+
const prBody = context.payload.pull_request.body || '';
21+
const prAuthor = context.payload.pull_request.user.login;
22+
const prNumber = context.payload.pull_request.number;
23+
const issuePattern = /(?:fix(?:es)?|close[sd]?|resolve[sd]?)\s+#(\d+)/gi;
24+
const matches = [...prBody.matchAll(issuePattern)];
25+
if (matches.length === 0) {
26+
console.log('No linked issues found in PR body');
27+
return;
28+
}
29+
const processedIssues = new Set();
30+
for (const match of matches) {
31+
const issueNumber = parseInt(match[1], 10);
32+
if (processedIssues.has(issueNumber)) {
33+
continue;
34+
}
35+
processedIssues.add(issueNumber);
36+
try {
37+
const issue = await github.rest.issues.get({
38+
owner: context.repo.owner,
39+
repo: context.repo.repo,
40+
issue_number: issueNumber
41+
});
42+
if (issue.data.pull_request) {
43+
console.log(`#${issueNumber} is a pull request, skipping`);
44+
continue;
45+
}
46+
if (issue.data.assignees && issue.data.assignees.length > 0) {
47+
const currentAssignees = issue.data.assignees.map(a => a.login);
48+
if (currentAssignees.includes(prAuthor)) {
49+
console.log(`Issue #${issueNumber} already assigned to ${prAuthor}`);
50+
continue;
51+
}
52+
console.log(`Issue #${issueNumber} already assigned to: ${currentAssignees.join(', ')}`);
53+
continue;
54+
}
55+
await github.rest.issues.addAssignees({
56+
owner: context.repo.owner,
57+
repo: context.repo.repo,
58+
issue_number: issueNumber,
59+
assignees: [prAuthor]
60+
});
61+
62+
console.log(`Assigned issue #${issueNumber} to ${prAuthor}`);
63+
await github.rest.issues.createComment({
64+
owner: context.repo.owner,
65+
repo: context.repo.repo,
66+
issue_number: issueNumber,
67+
body: `This issue has been automatically assigned to @${prAuthor} who opened PR #${prNumber} to address it. 🎯`
68+
});
69+
70+
} catch (error) {
71+
if (error.status === 404) {
72+
console.log(`Issue #${issueNumber} not found`);
73+
} else {
74+
console.error(`Error processing issue #${issueNumber}:`, error.message);
75+
}
76+
}
77+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
name: PR Reopen Reassignment
2+
3+
on:
4+
pull_request_target:
5+
types: [reopened]
6+
issue_comment:
7+
types: [created]
8+
push:
9+
10+
permissions:
11+
issues: write
12+
pull-requests: write
13+
14+
jobs:
15+
reassign-on-reopen:
16+
runs-on: ubuntu-latest
17+
if: github.event_name == 'pull_request_target' && github.event.action == 'reopened'
18+
steps:
19+
- name: Reassign issues on PR reopen
20+
uses: actions/github-script@v7
21+
with:
22+
github-token: ${{ secrets.GITHUB_TOKEN }}
23+
script: |
24+
const prBody = context.payload.pull_request.body || '';
25+
const prAuthor = context.payload.pull_request.user.login;
26+
const prNumber = context.payload.pull_request.number;
27+
const issuePattern = /(?:fix(?:es)?|close[sd]?|resolve[sd]?)\s+#(\d+)/gi;
28+
const matches = [...prBody.matchAll(issuePattern)];
29+
if (matches.length === 0) {
30+
console.log('No linked issues found in PR body');
31+
return;
32+
}
33+
const processedIssues = new Set();
34+
for (const match of matches) {
35+
const issueNumber = parseInt(match[1], 10);
36+
if (processedIssues.has(issueNumber)) {
37+
continue;
38+
}
39+
processedIssues.add(issueNumber);
40+
try {
41+
const issue = await github.rest.issues.get({
42+
owner: context.repo.owner,
43+
repo: context.repo.repo,
44+
issue_number: issueNumber
45+
});
46+
if (issue.data.pull_request) {
47+
continue;
48+
}
49+
const currentAssignees = issue.data.assignees?.map(a => a.login) || [];
50+
if (currentAssignees.length > 0 && !currentAssignees.includes(prAuthor)) {
51+
console.log(`Issue #${issueNumber} is assigned to others, skipping`);
52+
continue;
53+
}
54+
if (!currentAssignees.includes(prAuthor)) {
55+
await github.rest.issues.addAssignees({
56+
owner: context.repo.owner,
57+
repo: context.repo.repo,
58+
issue_number: issueNumber,
59+
assignees: [prAuthor]
60+
});
61+
console.log(`Reassigned issue #${issueNumber} to ${prAuthor}`);
62+
await github.rest.issues.createComment({
63+
owner: context.repo.owner,
64+
repo: context.repo.repo,
65+
issue_number: issueNumber,
66+
body: `Welcome back, @${prAuthor}! 🎉 This issue has been reassigned to you as you've reopened PR #${prNumber}.`
67+
});
68+
}
69+
} catch (error) {
70+
console.error(`Error processing issue #${issueNumber}:`, error.message);
71+
}
72+
}
73+
try {
74+
await github.rest.issues.removeLabel({
75+
owner: context.repo.owner,
76+
repo: context.repo.repo,
77+
issue_number: prNumber,
78+
name: 'stale'
79+
});
80+
} catch (e) {
81+
82+
}
83+
84+
handle-contributor-activity:
85+
runs-on: ubuntu-latest
86+
if: |
87+
github.event_name == 'issue_comment' &&
88+
github.event.issue.pull_request != null
89+
steps:
90+
- name: Check and handle stale PR activity
91+
uses: actions/github-script@v7
92+
with:
93+
github-token: ${{ secrets.GITHUB_TOKEN }}
94+
script: |
95+
const prNumber = context.payload.issue.number;
96+
const commenter = context.payload.comment.user.login;
97+
const { data: pr } = await github.rest.pulls.get({
98+
owner: context.repo.owner,
99+
repo: context.repo.repo,
100+
pull_number: prNumber
101+
});
102+
if (commenter !== pr.user.login) {
103+
console.log('Comment not from PR author, skipping');
104+
return;
105+
}
106+
const labels = context.payload.issue.labels?.map(l => l.name) || [];
107+
if (!labels.includes('stale')) {
108+
console.log('PR is not stale, skipping');
109+
return;
110+
}
111+
try {
112+
await github.rest.issues.removeLabel({
113+
owner: context.repo.owner,
114+
repo: context.repo.repo,
115+
issue_number: prNumber,
116+
name: 'stale'
117+
});
118+
console.log('Removed stale label');
119+
} catch (e) {
120+
console.log('Could not remove stale label:', e.message);
121+
}
122+
const prBody = pr.body || '';
123+
const issuePattern = /(?:fix(?:es)?|close[sd]?|resolve[sd]?)\s+#(\d+)/gi;
124+
const matches = [...prBody.matchAll(issuePattern)];
125+
for (const match of matches) {
126+
const issueNumber = parseInt(match[1], 10);
127+
try {
128+
const issue = await github.rest.issues.get({
129+
owner: context.repo.owner,
130+
repo: context.repo.repo,
131+
issue_number: issueNumber
132+
});
133+
if (issue.data.pull_request) {
134+
continue;
135+
}
136+
const currentAssignees = issue.data.assignees?.map(a => a.login) || [];
137+
if (currentAssignees.length === 0) {
138+
await github.rest.issues.addAssignees({
139+
owner: context.repo.owner,
140+
repo: context.repo.repo,
141+
issue_number: issueNumber,
142+
assignees: [commenter]
143+
});
144+
console.log(`Reassigned issue #${issueNumber} to ${commenter}`);
145+
}
146+
} catch (error) {
147+
console.error(`Error reassigning issue #${issueNumber}:`, error.message);
148+
}
149+
}
150+
await github.rest.issues.createComment({
151+
owner: context.repo.owner,
152+
repo: context.repo.repo,
153+
issue_number: prNumber,
154+
body: `Thanks for following up, @${commenter}! 🙌 The stale status has been removed and the linked issue(s) have been reassigned to you. Looking forward to your updates!`
155+
});

0 commit comments

Comments
 (0)