Skip to content

Commit cf97865

Browse files
authored
Merge pull request #64 from rtibblesbot/issue-62-1abd3e
feat: /assign self-assignment for good first issue (#62)
2 parents 5512260 + 43626d4 commit cf97865

12 files changed

Lines changed: 572 additions & 40 deletions
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
name: Manage issue header
1+
name: Handle issue label events
22
on:
33
issues:
4-
types: [opened, reopened, labeled, unlabeled]
4+
types: [labeled, unlabeled]
55
jobs:
66
call-workflow:
77
name: Call shared workflow
8-
uses: learningequality/.github/.github/workflows/manage-issue-header.yml@main
8+
uses: learningequality/.github/.github/workflows/issue-label.yml@main
99
secrets:
1010
LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }}
1111
LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name: Handle issue open events
2+
on:
3+
issues:
4+
types: [opened, reopened]
5+
jobs:
6+
call-workflow:
7+
name: Call shared workflow
8+
uses: learningequality/.github/.github/workflows/issue-open.yml@main
9+
secrets:
10+
LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }}
11+
LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Post good first issue guidance comment
2+
on:
3+
workflow_call:
4+
secrets:
5+
LE_BOT_APP_ID:
6+
description: 'GitHub App ID for authentication'
7+
required: true
8+
LE_BOT_PRIVATE_KEY:
9+
description: 'GitHub App Private Key for authentication'
10+
required: true
11+
jobs:
12+
good-first-issue-comment:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Generate GitHub token
16+
id: generate-token
17+
uses: actions/create-github-app-token@v3
18+
with:
19+
app-id: ${{ secrets.LE_BOT_APP_ID }}
20+
private-key: ${{ secrets.LE_BOT_PRIVATE_KEY }}
21+
- name: Checkout .github repository
22+
uses: actions/checkout@v6
23+
with:
24+
repository: learningequality/.github
25+
ref: main
26+
token: ${{ steps.generate-token.outputs.token }}
27+
- name: Setup Node.js
28+
uses: actions/setup-node@v6
29+
with:
30+
node-version: 20
31+
cache: 'yarn'
32+
- name: Install dependencies
33+
run: yarn install --frozen-lockfile
34+
- name: Run script
35+
uses: actions/github-script@v8
36+
with:
37+
github-token: ${{ steps.generate-token.outputs.token }}
38+
script: |
39+
const script = require('./scripts/good-first-issue-comment.js');
40+
await script({github, context, core});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Handle issue label events
2+
on:
3+
workflow_call:
4+
secrets:
5+
LE_BOT_APP_ID:
6+
description: 'GitHub App ID for authentication'
7+
required: true
8+
LE_BOT_PRIVATE_KEY:
9+
description: 'GitHub App Private Key for authentication'
10+
required: true
11+
jobs:
12+
manage-issue-header:
13+
name: Manage issue header
14+
if: >-
15+
(github.event.action == 'labeled' && github.event.label.name == 'help wanted') || (github.event.action == 'unlabeled' && github.event.label.name == 'help wanted')
16+
uses: learningequality/.github/.github/workflows/manage-issue-header.yml@main
17+
secrets:
18+
LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }}
19+
LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }}
20+
good-first-issue-comment:
21+
name: Post good first issue guidance comment
22+
if: >-
23+
github.event.action == 'labeled' && (github.event.label.name == 'good first issue' || github.event.label.name == 'help wanted')
24+
uses: learningequality/.github/.github/workflows/good-first-issue-comment.yml@main
25+
secrets:
26+
LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }}
27+
LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: Handle issue open events
2+
on:
3+
workflow_call:
4+
secrets:
5+
LE_BOT_APP_ID:
6+
description: 'GitHub App ID for authentication'
7+
required: true
8+
LE_BOT_PRIVATE_KEY:
9+
description: 'GitHub App Private Key for authentication'
10+
required: true
11+
jobs:
12+
manage-issue-header:
13+
name: Manage issue header
14+
uses: learningequality/.github/.github/workflows/manage-issue-header.yml@main
15+
secrets:
16+
LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }}
17+
LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }}

β€Ž.github/workflows/manage-issue-header.ymlβ€Ž

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@ on:
1111
jobs:
1212
manage-issue-header:
1313
runs-on: ubuntu-latest
14-
if: |
15-
github.event.action == 'opened' ||
16-
github.event.action == 'reopened' ||
17-
(github.event.action == 'labeled' && github.event.label.name == 'help wanted') ||
18-
(github.event.action == 'unlabeled' && github.event.label.name == 'help wanted')
1914
steps:
2015
- name: Generate GitHub token
2116
id: generate-token

β€Ždocs/community-automations.mdβ€Ž

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,61 @@
33
Manages GitHub issue comments. Sends Slack notifications and GitHub bot replies.
44

55
| Contributor type | Issue type | Comment type | #support-dev | #support-dev-notifications | GitHub bot | GitHub bot message |
6-
|------------------|------------|--------------|--------------|---------------------------|------------------|-------------|
6+
|------------------|------------|--------------|--------------|---------------------------|------------|-------------------|
77
| **Core team** | Any | Any | No | No | No | - |
8-
| **Close contributor** | Any | Any | **Yes** | No | No | - |
9-
| **Issue creator** | `help-wanted` | Any | **Yes** | No | No | - |
10-
| **Issue creator** | Private | Any | No | Yes | No | - |
11-
| **Other** | Private | Regular | No | Yes | No | - |
12-
| **Other** | Private | Assignment request | No | Yes | Yes`*` | `BOT_MESSAGE_ISSUE_NOT_OPEN` |
13-
| **Other** | Unassigned `help-wanted` | Any | **Yes** | No | No | - |
14-
| **Other** | `help-wanted` assigned to the comment author | Any | **Yes** | No | No | - |
15-
| **Other** | `help-wanted` assigned to someone else | Regular | No | Yes | No | - |
16-
| **Other** | `help-wanted` assigned to someone else | Assignment request | No | Yes | Yes`*` | `BOT_MESSAGE_ALREADY_ASSIGNED` |
8+
| **Close contributor** | Any | regular, [assign keyword](https://github.com/learningequality/.github/blob/main/scripts/constants.js#L44) | **Yes** | No | No | - |
9+
| **Issue creator** | `help-wanted` | regular, [assign keyword](https://github.com/learningequality/.github/blob/main/scripts/constants.js#L44) | **Yes** | No | No | - |
10+
| **Issue creator** | Private | regular, [assign keyword](https://github.com/learningequality/.github/blob/main/scripts/constants.js#L44) | No | Yes | No | - |
11+
| **Other** | Private | regular | No | Yes | No | - |
12+
| **Other** | Private | [assign keyword](https://github.com/learningequality/.github/blob/main/scripts/constants.js#L44) | No | Yes | Yes`*` | `BOT_MESSAGE_ISSUE_NOT_OPEN` |
13+
| **Other** | Unassigned `help-wanted` (not good first issue) | regular, [assign keyword](https://github.com/learningequality/.github/blob/main/scripts/constants.js#L44) | **Yes** | No | No | - |
14+
| **Other** | Unassigned `help-wanted` + good first issue | regular | **Yes** | No | No | - |
15+
| **Other** | Unassigned `help-wanted` + good first issue | [assign keyword](https://github.com/learningequality/.github/blob/main/scripts/constants.js#L44) | **Yes** | No | Yes`*` | `BOT_MESSAGE_KEYWORD_GOOD_FIRST_ISSUE` |
16+
| **Other** | `help-wanted` assigned to commenter | regular, [assign keyword](https://github.com/learningequality/.github/blob/main/scripts/constants.js#L44) | **Yes** | No | No | - |
17+
| **Other** | `help-wanted` assigned to someone else | regular | No | Yes | No | - |
18+
| **Other** | `help-wanted` assigned to someone else | [assign keyword](https://github.com/learningequality/.github/blob/main/scripts/constants.js#L44) | No | Yes | Yes`*` | `BOT_MESSAGE_ALREADY_ASSIGNED` |
19+
| **Close contributor** | Private | `/assign` command | No | Yes | Yes | `BOT_MESSAGE_ISSUE_NOT_OPEN` |
20+
| **Close contributor** | `help-wanted` assigned to commenter | `/assign` command | No | No | No | - (silent no-op) |
21+
| **Close contributor** | `help-wanted` assigned to someone else | `/assign` command | No | Yes | Yes | `BOT_MESSAGE_ALREADY_ASSIGNED` |
22+
| **Close contributor** | Unassigned `help-wanted` (not good first issue) | `/assign` command | No | Yes | Yes | `BOT_MESSAGE_ASSIGN_NOT_GOOD_FIRST_ISSUE` |
23+
| **Close contributor** | Unassigned `help-wanted` + good first issue, under limit | `/assign` command | No | Yes | Yes | `BOT_MESSAGE_ASSIGN_SUCCESS` |
24+
| **Close contributor** | Unassigned `help-wanted` + good first issue, at limit | `/assign` command | No | Yes | Yes | Dynamic at-limit message |
25+
| **Issue creator** | Private | `/assign` command | No | Yes | Yes | `BOT_MESSAGE_ISSUE_NOT_OPEN` |
26+
| **Issue creator** | `help-wanted` assigned to commenter | `/assign` command | No | No | No | - (silent no-op) |
27+
| **Issue creator** | `help-wanted` assigned to someone else | `/assign` command | No | Yes | Yes | `BOT_MESSAGE_ALREADY_ASSIGNED` |
28+
| **Issue creator** | Unassigned `help-wanted` (not good first issue) | `/assign` command | No | Yes | Yes | `BOT_MESSAGE_ASSIGN_NOT_GOOD_FIRST_ISSUE` |
29+
| **Issue creator** | Unassigned `help-wanted` + good first issue, under limit | `/assign` command | No | Yes | Yes | `BOT_MESSAGE_ASSIGN_SUCCESS` |
30+
| **Issue creator** | Unassigned `help-wanted` + good first issue, at limit | `/assign` command | No | Yes | Yes | Dynamic at-limit message |
31+
| **Other** | Private | `/assign` command | No | Yes | Yes | `BOT_MESSAGE_ISSUE_NOT_OPEN` |
32+
| **Other** | `help-wanted` assigned to commenter | `/assign` command | No | No | No | - (silent no-op) |
33+
| **Other** | `help-wanted` assigned to someone else | `/assign` command | No | Yes | Yes | `BOT_MESSAGE_ALREADY_ASSIGNED` |
34+
| **Other** | Unassigned `help-wanted` (not good first issue) | `/assign` command | No | Yes | Yes | `BOT_MESSAGE_ASSIGN_NOT_GOOD_FIRST_ISSUE` |
35+
| **Other** | Unassigned `help-wanted` + good first issue, under limit | `/assign` command | No | Yes | Yes | `BOT_MESSAGE_ASSIGN_SUCCESS` |
36+
| **Other** | Unassigned `help-wanted` + good first issue, at limit | `/assign` command | No | Yes | Yes | Dynamic at-limit message |
1737

1838
`*` There is an additional optimization that prevents more than one bot message per hour to not overwhelm issue comment section
1939

20-
In `scripts/contants.js` set:
40+
**`/assign` command** applies to all external contributors (close contributors, issue creators). Detected when the comment contains `/assign` as a standalone word (`/assign me` matches, `/assignee` does not). All `/assign` Slack activity goes to `#support-dev-notifications` only.
41+
42+
**`/assign` cross-repo limit:** Contributors can have up to 2 assigned issues across all community repos (`COMMUNITY_REPOS`). Issues unassigned within the last 7 days count toward the limit (`currentAssignments + recentUnassignments >= MAX_ASSIGNED_ISSUES`).
43+
44+
In `scripts/constants.js` set:
2145
- `BOT_MESSAGE_ISSUE_NOT_OPEN`: _Issue not open for contribution_ message text
2246
- `BOT_MESSAGE_ALREADY_ASSIGNED`: _Issue already assigned_ message text
47+
- `BOT_MESSAGE_ASSIGN_SUCCESS`: Assignment confirmation message
48+
- `BOT_MESSAGE_ASSIGN_NOT_GOOD_FIRST_ISSUE`: Decline message for non-good-first-issue issues
49+
- `BOT_MESSAGE_KEYWORD_GOOD_FIRST_ISSUE`: Keyword reply with `/assign` guidance
50+
51+
# `good-first-issue-comment`
52+
53+
Posts a guidance comment when the `good first issue` label is applied to an issue (triggered via the `issue-label` workflow). Explains the `/assign` command, issue limits, cooldown, and links to contributing guidelines.
54+
55+
- Only posts if the issue also has `help wanted` label
56+
- Deletes any previous guidance comment from the bot before posting a new one
57+
- Identified by the `<!-- ASSIGN_GUIDANCE -->` HTML comment marker
58+
59+
In `scripts/constants.js` set:
60+
- `BOT_MESSAGE_GOOD_FIRST_ISSUE_GUIDANCE`: Guidance message text
2361

2462
# `contributor-pr-reply`
2563

β€Žscripts/constants.jsβ€Ž

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ const KEYWORDS_DETECT_ASSIGNMENT_REQUEST = [
8282
];
8383

8484
const ISSUE_LABEL_HELP_WANTED = 'help wanted';
85+
const ISSUE_LABEL_GOOD_FIRST_ISSUE = 'good first issue';
86+
const MAX_ASSIGNED_ISSUES = 2;
87+
const ASSIGN_COOLDOWN_DAYS = 7;
88+
const ASSIGN_GUIDANCE_MARKER = '<!-- ASSIGN_GUIDANCE -->';
8589
const LABEL_COMMUNITY_REVIEW = 'community-review';
8690

8791
// Will be attached to bot messages when not empty
@@ -92,7 +96,52 @@ const BOT_MESSAGE_ISSUE_NOT_OPEN = `Hi! πŸ‘‹ \n\n Thanks so much for your intere
9296

9397
const BOT_MESSAGE_ALREADY_ASSIGNED = `Hi! πŸ‘‹ \n\n Thanks so much for your interest! **This issue is already assigned. Visit [Contributing guidelines](https://learningequality.org/contributing-to-our-open-code-base) to learn about the contributing process and how to find suitable issues. If there are no unassigned 'help wanted' issues available, please wait until new ones are added.** \n\n We really appreciate your willingness to help. 😊${GSOC_NOTE}`;
9498

95-
const BOT_MESSAGE_PULL_REQUEST = (author) => `πŸ‘‹ Hi @${author}, thanks for contributing! \n\n **For the review process to begin, please verify that the following is satisfied:**\n\n- [ ] **Contribution is aligned with our [contributing guidelines](https://learningequality.org/contributing-to-our-open-code-base)**\n- [ ] **Pull request description has correctly filled _AI usage_ section & follows our AI guidance:**\n\n <details>\n <summary><b><i>AI guidance</i></b></summary>\n\n <br>\n\n **State explicitly whether you didn't use or used AI & how.**\n\n If you used it, ensure that the PR is aligned with [Using AI](https://learningequality.org/contributing-to-our-open-code-base/#using-generative-ai) as well as our DEEP framework. DEEP asks you:\n\n - **Disclose** β€” Be open about when you've used AI for support.\n - **Engage critically** β€” Question what is generated. Review code for correctness and unnecessary complexity.\n - **Edit** β€” Review and refine AI output. Remove unnecessary code and verify it still works after your edits.\n - **Process sharing** β€” Explain how you used the AI so others can learn.\n\n <br>\n\n Examples of good disclosures:\n\n > "I used Claude Code to implement the component, prompting it to follow the pattern in ComponentX. I reviewed the generated code, removed unnecessary error handling, and verified the tests pass."\n\n > "I brainstormed the approach with Gemini, then had it write failing tests for the feature. After reviewing the tests, I used Claude Code to generate the implementation. I refactored the output to reduce verbosity and ran the full test suite."\n\n </details>\n\nAlso check that issue requirements are satisfied & you ran \`pre-commit\` locally. \n\n**Pull requests that don't follow the guidelines will be closed.**\n\n**Reviewer assignment can take up to 2 weeks.**`;
99+
const BOT_MESSAGE_GOOD_FIRST_ISSUE_GUIDANCE =
100+
`${ASSIGN_GUIDANCE_MARKER}\n\n` +
101+
`Hi! πŸ‘‹\n\n` +
102+
`This issue is available for contribution and supports ` +
103+
`**self-assignment**. Here's how to get started:\n\n` +
104+
`- **Comment \`/assign\` to assign yourself** to this issue\n` +
105+
`- You can have up to **${MAX_ASSIGNED_ISSUES} issues** assigned ` +
106+
`at a time across all community repos\n` +
107+
`- Dropping an issue has a **${ASSIGN_COOLDOWN_DAYS}-day cooldown** ` +
108+
`before the slot opens up\n` +
109+
`- **Link your pull request** to this issue when you submit it` +
110+
`\n\nπŸ“– **Read the [Contributing guidelines]` +
111+
`(https://learningequality.org/contributing-to-our-open-code-base/)` +
112+
` before starting.**${GSOC_NOTE}`;
113+
114+
const BOT_MESSAGE_ASSIGN_SUCCESS =
115+
`Hi! πŸ‘‹\n\n` +
116+
`You've been assigned to this issue. Here's what to do next:\n\n` +
117+
`- **Read the issue description** carefully and make sure ` +
118+
`you understand the requirements\n` +
119+
`- **Link your pull request** to this issue when you submit it\n` +
120+
`- If you can no longer work on this, **unassign yourself** ` +
121+
`so others can pick it up\n\n` +
122+
`Good luck! 😊${GSOC_NOTE}`;
123+
124+
const BOT_MESSAGE_ASSIGN_NOT_GOOD_FIRST_ISSUE =
125+
`Hi! πŸ‘‹\n\n` +
126+
`Self-assignment via \`/assign\` is only available for issues ` +
127+
`labeled **\`good first issue\`**. This issue does not have ` +
128+
`that label.\n\n` +
129+
`Visit [Contributing guidelines]` +
130+
`(https://learningequality.org/contributing-to-our-open-code-base/)` +
131+
` to learn about the contributing process and how to find ` +
132+
`suitable issues. 😊${GSOC_NOTE}`;
133+
134+
const BOT_MESSAGE_KEYWORD_GOOD_FIRST_ISSUE =
135+
`Hi! πŸ‘‹\n\n` +
136+
`Thanks for your interest! This issue supports ` +
137+
`**self-assignment**. **Comment \`/assign\` to assign ` +
138+
`yourself.**\n\n` +
139+
`Visit [Contributing guidelines]` +
140+
`(https://learningequality.org/contributing-to-our-open-code-base/)` +
141+
` to learn about the contributing process. 😊${GSOC_NOTE}`;
142+
143+
const BOT_MESSAGE_PULL_REQUEST = author =>
144+
`πŸ‘‹ Hi @${author}, thanks for contributing! \n\n **For the review process to begin, please verify that the following is satisfied:**\n\n- [ ] **Contribution is aligned with our [contributing guidelines](https://learningequality.org/contributing-to-our-open-code-base)**\n- [ ] **Pull request description has correctly filled _AI usage_ section & follows our AI guidance:**\n\n <details>\n <summary><b><i>AI guidance</i></b></summary>\n\n <br>\n\n **State explicitly whether you didn't use or used AI & how.**\n\n If you used it, ensure that the PR is aligned with [Using AI](https://learningequality.org/contributing-to-our-open-code-base/#using-generative-ai) as well as our DEEP framework. DEEP asks you:\n\n - **Disclose** β€” Be open about when you've used AI for support.\n - **Engage critically** β€” Question what is generated. Review code for correctness and unnecessary complexity.\n - **Edit** β€” Review and refine AI output. Remove unnecessary code and verify it still works after your edits.\n - **Process sharing** β€” Explain how you used the AI so others can learn.\n\n <br>\n\n Examples of good disclosures:\n\n > "I used Claude Code to implement the component, prompting it to follow the pattern in ComponentX. I reviewed the generated code, removed unnecessary error handling, and verified the tests pass."\n\n > "I brainstormed the approach with Gemini, then had it write failing tests for the feature. After reviewing the tests, I used Claude Code to generate the implementation. I refactored the output to reduce verbosity and ran the full test suite."\n\n </details>\n\nAlso check that issue requirements are satisfied & you ran \`pre-commit\` locally. \n\n**Pull requests that don't follow the guidelines will be closed.**\n\n**Reviewer assignment can take up to 2 weeks.**`;
96145

97146
const HOLIDAY_MESSAGE = `Season's greetings! πŸ‘‹ \n\n We'd like to thank everyone for another year of fruitful collaborations, engaging discussions, and for the continued support of our work. **Learning Equality will be on holidays from December 22 to January 5.** We look forward to much more in the new year and wish you a very happy holiday season!${GSOC_NOTE}`;
98147

@@ -119,8 +168,17 @@ module.exports = {
119168
CLOSE_CONTRIBUTORS,
120169
KEYWORDS_DETECT_ASSIGNMENT_REQUEST,
121170
ISSUE_LABEL_HELP_WANTED,
171+
GSOC_NOTE,
172+
ISSUE_LABEL_GOOD_FIRST_ISSUE,
173+
MAX_ASSIGNED_ISSUES,
174+
ASSIGN_COOLDOWN_DAYS,
175+
ASSIGN_GUIDANCE_MARKER,
122176
BOT_MESSAGE_ISSUE_NOT_OPEN,
123177
BOT_MESSAGE_ALREADY_ASSIGNED,
178+
BOT_MESSAGE_GOOD_FIRST_ISSUE_GUIDANCE,
179+
BOT_MESSAGE_ASSIGN_SUCCESS,
180+
BOT_MESSAGE_ASSIGN_NOT_GOOD_FIRST_ISSUE,
181+
BOT_MESSAGE_KEYWORD_GOOD_FIRST_ISSUE,
124182
BOT_MESSAGE_PULL_REQUEST,
125183
BOT_MESSAGE_RTIBBLESBOT_REVIEW,
126184
RTIBBLESBOT_USERNAME,

0 commit comments

Comments
Β (0)