Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c95f427
updated notify team new comment workflow
iamshobhraj Jun 25, 2025
c71409e
updated the name of secrets for LE bot
iamshobhraj Jun 25, 2025
74c7cbd
updated the name of secrets for LE bot
iamshobhraj Jun 25, 2025
54ae934
updated bot username
iamshobhraj Jun 28, 2025
ee55567
fixed the spelling of dependency
iamshobhraj Jun 28, 2025
add029b
added fallback for bot create comment api call
iamshobhraj Jun 28, 2025
41deb3a
added fallback for fetchig labels on the issue api call
iamshobhraj Jun 28, 2025
abf3932
added fallback for fetchig comments on the issue api call
iamshobhraj Jun 28, 2025
a7e40da
linting fix
iamshobhraj Jun 28, 2025
1989bab
reduced bigger fn into smaller ones
iamshobhraj Jun 28, 2025
5d717f4
fixed promise errors and reorganised code
iamshobhraj Jun 30, 2025
5e36f17
added regex to check matched keywords
iamshobhraj Jun 30, 2025
c816e26
removed extra lines
iamshobhraj Jun 30, 2025
b93ac48
moved literal constants to seperate file
iamshobhraj Jun 30, 2025
a1d6dbf
updated bot username
iamshobhraj Jul 1, 2025
ccde01a
added seperate file for close contributors
iamshobhraj Jul 1, 2025
8f7517b
updated node version
iamshobhraj Jul 15, 2025
7e7a36b
updated secrets variable name
iamshobhraj Jul 15, 2025
dc2a320
updated the bot reply step name
iamshobhraj Jul 15, 2025
e3e34ab
renamed a step
iamshobhraj Jul 15, 2025
e9741f4
renamed a output variable name for message
iamshobhraj Jul 15, 2025
c1e13f5
renamed a output variable name for bot message
iamshobhraj Jul 15, 2025
b4d5d66
cleaned up keywords.txt
iamshobhraj Jul 15, 2025
5f4c998
Adjust bot message
MisRob Jul 21, 2025
53fdd72
Cleanup keywords
MisRob Jul 21, 2025
0dd8c72
Minor naming and documentation tweaks
MisRob Jul 21, 2025
0713ff5
Consolidate constants
MisRob Jul 21, 2025
5a073d1
Rename variable
MisRob Jul 21, 2025
3437b1b
Re-organize workflow
MisRob Jul 21, 2025
a45a3ad
Add close contributors
MisRob Jul 21, 2025
734b989
Reference learningequality org
MisRob Jul 21, 2025
67ea64b
Fix bot name
MisRob Jul 21, 2025
8aceb70
Rename script
MisRob Jul 21, 2025
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
14 changes: 14 additions & 0 deletions .github/workflows/call-contributor-issue-comment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Handle contributor comment on GitHub issue

on:
issue_comment:
types: [created]

jobs:
call-workflow:
uses: learningequality/.github/.github/workflows/contributor-issue-comment.yml@main
secrets:
LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }}
LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL: ${{ secrets.SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL }}
89 changes: 89 additions & 0 deletions .github/workflows/contributor-issue-comment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: Handle contributor comment on GitHub issue

on:
workflow_call:
secrets:
LE_BOT_APP_ID:
description: "GitHub App ID for authentication"
required: true
LE_BOT_PRIVATE_KEY:
description: "GitHub App Private Key for authentication"
required: true
SLACK_WEBHOOK_URL:
required: true
description: "Webhook URL for Slack #support-dev channel"
SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL:
required: true
description: "Webhook URL for Slack #support-dev-notifications channel"


jobs:
process-issue-comment:
name: Process issue comment

if: >-
${{
!github.event.issue.pull_request &&
github.event.comment.author_association != 'MEMBER' &&
github.event.comment.author_association != 'OWNER' &&
github.event.comment.user.login != 'sentry-io[bot]' &&
github.event.comment.user.login != 'learning-equality-bot[bot]'
}}

runs-on: ubuntu-latest
steps:
- name: Generate App Token
id: generate-token
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.LE_BOT_APP_ID }}
private_key: ${{ secrets.LE_BOT_PRIVATE_KEY }}

- name: Checkout .github repository
uses: actions/checkout@v4
with:
repository: learningequality/.github
ref: main
token: ${{ steps.generate-token.outputs.token }}

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

- name: Install dependencies
run: npm install

- name: Run script
id: script
uses: actions/github-script@v7
with:
github-token: ${{ steps.generate-token.outputs.token }}
script: |
const script = require('./scripts/contributor-issue-comment.js');
return await script({github, context, core});
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL: ${{ secrets.SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL }}

- name: Send Slack notification about GitHub comment
uses: slackapi/slack-github-action@v2.1.0
with:
webhook-type: incoming-webhook
webhook: ${{ steps.script.outputs.webhook_url }}
payload: >
{
"text": "${{ steps.script.outputs.slack_notification_comment }}"
}

- name: Send Slack notification about GitHub bot reply
if: ${{ steps.script.outputs.bot_replied }}
uses: slackapi/slack-github-action@v2.1.0
with:
webhook-type: incoming-webhook
webhook: ${{ steps.script.outputs.webhook_url }}
payload: >
{
"text": "${{ steps.script.outputs.slack_notification_bot_comment }}"
}

37 changes: 0 additions & 37 deletions .github/workflows/notify_team_new_comment.yml

This file was deleted.

42 changes: 42 additions & 0 deletions scripts/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const LE_BOT_USERNAME = 'learning-equality-bot[bot]';

// close contributors are treated a bit special in some workflows,
// for example, we receive a high priority notification about their
// comments on all issues rather than just on 'help wanted' issues
const CLOSE_CONTRIBUTORS = ['BabyElias', 'Dimi20cen', 'EshaanAgg', 'GarvitSinghal47', 'habibayman', 'iamshobhraj', 'indirectlylit', 'Jakoma02', 'KshitijThareja', 'muditchoudhary', 'nathanaelg16', 'nikkuAg', 'Sahil-Sinha-11', 'shivam-daksh', 'shruti862', 'thesujai', 'WinnyChang'];

const KEYWORDS_DETECT_ASSIGNMENT_REQUEST = [
'assign', 'assigned',
'work', 'working',
'contribute', 'contributing',
'request', 'requested',
'pick', 'picked', 'picking',
'address', 'addressing',
'handle', 'handling',
'solve', 'solving', 'resolve', 'resolving',
'try', 'trying',
'grab', 'grabbing',
'claim', 'claimed',
'interest', 'interested',
'do', 'doing',
'help',
'take',
'want',
'would like',
'own',
'on it',
'available',
'got this'
];

const ISSUE_LABEL_HELP_WANTED = 'help wanted';

const BOT_MESSAGE_ISSUE_NOT_OPEN = `Hi! 👋 \n\n Thanks so much for your interest! **This issue is not open for contribution. Visit [Contributing guidelines](https://learningequality.org/contributing-to-our-open-code-base) to learn about the contributing process and how to find suitable issues.** \n\n We really appreciate your willingness to help—you're welcome to find a more suitable issue, and let us know if you have any questions. 😊`;

module.exports = {
LE_BOT_USERNAME,
CLOSE_CONTRIBUTORS,
KEYWORDS_DETECT_ASSIGNMENT_REQUEST,
ISSUE_LABEL_HELP_WANTED,
BOT_MESSAGE_ISSUE_NOT_OPEN,
};
109 changes: 109 additions & 0 deletions scripts/contributor-issue-comment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
const {
LE_BOT_USERNAME,
CLOSE_CONTRIBUTORS,
KEYWORDS_DETECT_ASSIGNMENT_REQUEST,
ISSUE_LABEL_HELP_WANTED,
BOT_MESSAGE_ISSUE_NOT_OPEN
} = require('./constants');

module.exports = async ({ github, context, core }) => {
try {
const issueNumber = context.payload.issue.number;
const issueUrl = context.payload.issue.html_url;
const issueTitle = context.payload.issue.title;
const escapedTitle = issueTitle.replace(/"/g, '\\"');
const commentId = context.payload.comment.id;
const commentTime = new Date(context.payload.comment.created_at);
const oneHourBefore = new Date(commentTime - 3600000);
const commentAuthor = context.payload.comment.user.login;
const commentBody = context.payload.comment.body;
const repo = context.repo.repo;
const owner = context.repo.owner;
const supportDevSlackWebhookUrl = process.env.SLACK_WEBHOOK_URL;
const supportDevNotificationsSlackWebhookUrl = process.env.SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL;
const keywordRegexes = KEYWORDS_DETECT_ASSIGNMENT_REQUEST
.map(k => k.trim().toLowerCase())
.filter(Boolean)
.map(keyword => new RegExp(`\\b${keyword}\\b`, 'i'));



async function hasLabel(name) {
let labels = [];
try {
const response = await github.rest.issues.listLabelsOnIssue({
owner,
repo,
issue_number: issueNumber
});
labels = response.data.map(label => label.name);
} catch (error) {
core.warning(`⚠️ Failed to fetch labels on issue #${issueNumber}: ${error.message}`);
labels = [];
}
return labels.some(label => label.toLowerCase() === name.toLowerCase());
}

async function findRecentCommentsByUser(username) {
try{
let response = await github.rest.issues.listComments({
owner,
repo,
issue_number: issueNumber,
since: oneHourBefore.toISOString()
});
return response.data.filter(comment => comment.user.login === username);
} catch (error) {
core.warning(`⚠️ Failed to fetch comments on issue #${issueNumber}: ${error.message}`);
return [];
}
}

async function botReply(){
let response = null;
try {
response = await github.rest.issues.createComment({
owner,
repo,
issue_number: issueNumber,
body: BOT_MESSAGE_ISSUE_NOT_OPEN
});
if (response?.data?.html_url) {
core.setOutput('bot_replied', true);
const slackMessage = `*[${repo}] <${response.data.html_url}|Bot response sent> on issue: <${issueUrl}|${escapedTitle}>*`;
core.setOutput('slack_notification_bot_comment', slackMessage);
}
} catch (error) {
core.warning(`Failed to post bot comment: ${error.message}`);
core.setOutput('bot_replied', false);
}
return response;
}


if (await hasLabel(ISSUE_LABEL_HELP_WANTED) || CLOSE_CONTRIBUTORS.includes(commentAuthor)) {
core.setOutput('webhook_url', supportDevSlackWebhookUrl);
} else {
core.setOutput('webhook_url', supportDevNotificationsSlackWebhookUrl);
const matchedKeyword = keywordRegexes.find(regex => regex.test(commentBody));
// post a bot reply if there is matched keyword and no previous bot comment in past hour
if(matchedKeyword){
let lastBotComment;
let PastBotComments = await findRecentCommentsByUser(LE_BOT_USERNAME);
if(PastBotComments.length > 0){
lastBotComment = PastBotComments.at(-1);
core.setOutput('bot_replied', false);
} else if(PastBotComments.length === 0){
console.log("bot is replying");
lastBotComment = await botReply();
}
}
}

const message = `*[${repo}] <${issueUrl}#issuecomment-${commentId}|New comment> on issue: <${issueUrl}|${escapedTitle}> by ${commentAuthor}*`;
core.setOutput('slack_notification_comment', message);

} catch (error) {
core.setFailed(`Action failed with error: ${error.message}`);
}
};