Skip to content

Commit 8ad550c

Browse files
committed
chore: add github templates and tasks automation workflows(pr review and issue assign)
1 parent f326946 commit 8ad550c

8 files changed

Lines changed: 394 additions & 0 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
name: Bug Report
3+
about: Reporting bugs in existing code
4+
title: ''
5+
labels: bug, good first issue
6+
assignees: ''
7+
8+
---
9+
10+
## The Problem
11+
12+
- describe in bullet points
13+
14+
## Suggested Solution (Optional)
15+
16+
- describe in bullet points
17+
18+
> [!NOTE]
19+
> **CONTRIBUTIONS ARE WELCOME!**
20+
> If you want to get this issue assigned to you. Just comment `assign this issue to me`. You will be assigned to the issue instantly via GitHub-actions bot.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
name: Documentation
3+
about: Report issues or additions to Documentation
4+
title: ''
5+
labels: documentation, good first issue
6+
assignees: ''
7+
8+
---
9+
10+
## Description of what to add
11+
12+
- Description in bullet points
13+
14+
> [!NOTE]
15+
> **CONTRIBUTIONS ARE WELCOME!**
16+
> If you want to get this issue assigned to you. Just comment `assign this issue to me`. You will be assigned to the issue instantly via GitHub-actions bot.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
name: Feature-Change Request
3+
about: Suggest a change in an existing feature
4+
title: ''
5+
labels: change, good first issue
6+
assignees: ''
7+
8+
---
9+
10+
## What needs to change
11+
12+
- describe in bullet points
13+
14+
## Why does it need to change
15+
16+
- describe in bullet points
17+
18+
> [!NOTE]
19+
> **CONTRIBUTIONS ARE WELCOME!**
20+
> If you want to get this issue assigned to you. Just comment `assign this issue to me`. You will be assigned to the issue instantly via GitHub-actions bot.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
name: Feature Request
3+
about: Suggest a new feature for this project
4+
title: ''
5+
labels: feature, good first issue
6+
assignees: ''
7+
8+
---
9+
10+
## Description of the feature
11+
12+
- describe in bullet points
13+
14+
## Suggested Solution (Optional)
15+
16+
- describe in bullet points
17+
18+
> [!NOTE]
19+
> **CONTRIBUTIONS ARE WELCOME!**
20+
> If you want to get this issue assigned to you. Just comment `assign this issue to me`. You will be assigned to the issue instantly via GitHub-actions bot.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
name: Refactoring Request
3+
about: Suggest refactoring or code quality improvements
4+
title: ''
5+
labels: refactoring, good first issue
6+
assignees: ''
7+
8+
---
9+
10+
## Area to Refactor
11+
12+
- mention the file(s), module(s), or component(s)
13+
14+
## Current Issues
15+
16+
- describe the problems in the existing implementation
17+
- e.g. complexity, duplication, readability, technical debt
18+
19+
## Proposed Refactoring (Optional)
20+
21+
- describe the refactoring approach in bullet points
22+
23+
24+
> [!NOTE]
25+
> **CONTRIBUTIONS ARE WELCOME!**
26+
> If you want to get this issue assigned to you, just comment `assign this issue to me`.
27+
> You will be assigned to the issue instantly via GitHub-actions bot.
28+

.github/pull_request_template.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Fixes #ISSUE_NUMBER_HERE
2+
3+
Additionally, at the time of submission of this PR:
4+
5+
- The referred issue is not blocked currently
6+
- All unittests passed after changes were made
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
name: auto_assign_issue
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
7+
permissions:
8+
issues: write
9+
pull-requests: read
10+
contents: read
11+
12+
jobs:
13+
assign:
14+
if: github.event.issue.pull_request == null
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Assign commenter
19+
uses: actions/github-script@v7
20+
with:
21+
script: |
22+
const owner = context.repo.owner;
23+
const repo = context.repo.repo;
24+
const issue_number = context.payload.issue.number;
25+
const assignee = context.payload.comment.user.login;
26+
27+
const comment_id = context.payload.comment.id;
28+
29+
const body = (context.payload.comment.body || '').toLowerCase();
30+
31+
// keyword check for auto-assigning
32+
const hasAssignKeyword =
33+
body.includes('assign me') ||
34+
body.includes('assign it to me') ||
35+
body.includes('assign this to me') ||
36+
body.includes('assign this issue to me') ||
37+
body.includes('assign the issue to me');
38+
39+
if (!hasAssignKeyword) {
40+
core.info('No assign keyword found.');
41+
return;
42+
}
43+
44+
// Helper: react to the triggering comment
45+
async function react(content) {
46+
try {
47+
await github.rest.reactions.createForIssueComment({
48+
owner,
49+
repo,
50+
comment_id,
51+
content, // "+1", "confused", "rocket", etc.
52+
});
53+
} catch (e) {
54+
// If reactions aren't permitted for some reason, don't fail the whole workflow.
55+
core.info(`Could not add reaction (${content}): ${e.message}`);
56+
}
57+
}
58+
59+
// 1) Don't reassign if already assigned
60+
const current = context.payload.issue.assignees?.map(a => a.login) || [];
61+
if (current.length > 0) {
62+
core.info(`Already assigned to: ${current.join(', ')}`);
63+
await react('confused');
64+
return;
65+
}
66+
67+
// Helper: format issue as a single markdown link line (prevents duplicate link/title rendering)
68+
function formatIssueLine(i) {
69+
return `- [#${i.number} — ${i.title}](${i.url})`;
70+
}
71+
72+
// Helper: suggest other unblocked issues
73+
async function getSuggestions(limit = 5) {
74+
// open issues, unassigned, not blocked
75+
const q = `repo:${owner}/${repo} is:issue is:open no:assignee -label:blocked`;
76+
const res = await github.rest.search.issuesAndPullRequests({ q, per_page: limit });
77+
return (res.data.items || []).map(i => ({
78+
number: i.number,
79+
title: i.title,
80+
url: i.html_url,
81+
}));
82+
}
83+
84+
// 2) Block if label "blocked" exists (and suggest alternatives)
85+
const labels = (context.payload.issue.labels || [])
86+
.map(l => (typeof l === 'string' ? l : l.name))
87+
.filter(Boolean)
88+
.map(l => l.toLowerCase());
89+
90+
if (labels.includes('blocked')) {
91+
const suggestions = await getSuggestions(5);
92+
const suggestionText = suggestions.length
93+
? suggestions.map(formatIssueLine).join('\n')
94+
: '_No unblocked, unassigned issues found right now._';
95+
96+
await github.rest.issues.createComment({
97+
owner, repo, issue_number,
98+
body: [
99+
`⛔ Sorry @${assignee} — this issue is currently **blocked** and can’t be claimed yet.`,
100+
``,
101+
`Here are some other **unblocked** issues you can pick up instead:`,
102+
suggestionText,
103+
].join('\n'),
104+
});
105+
106+
await react('confused');
107+
return;
108+
}
109+
110+
// 3) Capacity check:
111+
// If the candidate has 2 assigned issues that do NOT have PRs, then do not assign a new issue.
112+
//
113+
// "Linked" here means: a PR cross-references the issue (GitHub timeline cross-reference),
114+
// and the PR is authored by the candidate.
115+
async function hasLinkedPRForIssue(issueNum) {
116+
const query = `
117+
query($owner: String!, $repo: String!, $number: Int!) {
118+
repository(owner: $owner, name: $repo) {
119+
issueOrPullRequest(number: $number) {
120+
__typename
121+
... on Issue {
122+
timelineItems(last: 50, itemTypes: [CROSS_REFERENCED_EVENT]) {
123+
nodes {
124+
__typename
125+
... on CrossReferencedEvent {
126+
source {
127+
__typename
128+
... on PullRequest {
129+
number
130+
url
131+
state
132+
isDraft
133+
author { login }
134+
}
135+
}
136+
}
137+
}
138+
}
139+
}
140+
}
141+
}
142+
}
143+
`;
144+
145+
const data = await github.graphql(query, { owner, repo, number: issueNum });
146+
147+
const iop = data?.repository?.issueOrPullRequest;
148+
if (!iop || iop.__typename !== 'Issue') return false;
149+
150+
const nodes = iop.timelineItems?.nodes || [];
151+
for (const n of nodes) {
152+
if (n?.__typename !== 'CrossReferencedEvent') continue;
153+
const pr = n?.source;
154+
if (pr?.__typename !== 'PullRequest') continue;
155+
156+
if ((pr?.author?.login || '').toLowerCase() === assignee.toLowerCase()) {
157+
// Counts any PR state (OPEN/MERGED/CLOSED) as "has a PR for it".
158+
return true;
159+
}
160+
}
161+
return false;
162+
}
163+
164+
async function getOpenAssignedIssuesForUser() {
165+
// Find open issues assigned to this user in this repo
166+
const q = `repo:${owner}/${repo} is:issue is:open assignee:${assignee}`;
167+
const res = await github.rest.search.issuesAndPullRequests({ q, per_page: 20 });
168+
return (res.data.items || []).map(i => ({
169+
number: i.number,
170+
title: i.title,
171+
url: i.html_url,
172+
}));
173+
}
174+
175+
const assignedIssues = await getOpenAssignedIssuesForUser();
176+
177+
// Count assigned issues that do NOT have PRs by this assignee
178+
const issuesWithoutPR = [];
179+
for (const it of assignedIssues) {
180+
const hasPR = await hasLinkedPRForIssue(it.number);
181+
if (!hasPR) issuesWithoutPR.push(it);
182+
if (issuesWithoutPR.length >= 2) break; // early exit
183+
}
184+
185+
if (issuesWithoutPR.length >= 2) {
186+
const noPRText = issuesWithoutPR
187+
.slice(0, 10)
188+
.map(formatIssueLine)
189+
.join('\n');
190+
191+
await github.rest.issues.createComment({
192+
owner, repo, issue_number,
193+
body: [
194+
`⛔ Sorry @${assignee} — you already have **2 or more** assigned issues with **no pull request(s)**.`,
195+
``,
196+
`Please open a PR for one of your assigned issues before claiming a new one:`,
197+
noPRText || '_Could not list the issues (unexpected)._',
198+
``,
199+
`Tip: opening a **draft PR** is fine—once a PR is linked, you'll be able to claim another issue.`,
200+
].join('\n'),
201+
});
202+
203+
await react('confused');
204+
return;
205+
}
206+
207+
// 4) Assign
208+
await github.rest.issues.addAssignees({
209+
owner, repo, issue_number,
210+
assignees: [assignee],
211+
});
212+
213+
await github.rest.issues.createComment({
214+
owner, repo, issue_number,
215+
body: `🎉 Thank you for your interest in contributing!\n\nThe issue has been assigned to @${assignee}. Happy coding!`,
216+
});
217+
218+
await react('rocket');

0 commit comments

Comments
 (0)